@@ -0,0 +1,251 @@ |
||
1 |
+{ |
|
2 |
+ "rulebook" : |
|
3 |
+ [ |
|
4 |
+ { |
|
5 |
+ "pageUrl" : "/rulebook/intro", |
|
6 |
+ "slug" : "intro", |
|
7 |
+ "title" : "Introduction", |
|
8 |
+ "tagline" : "What is Avalanche Network?", |
|
9 |
+ "content" : "data/rulebook/intro.md" |
|
10 |
+ }, |
|
11 |
+ { |
|
12 |
+ "pageUrl" : "/rulebook/missions", |
|
13 |
+ "slug" : "missions", |
|
14 |
+ "title" : "Mission Basics", |
|
15 |
+ "tagline" : "Mechanics of how a mission works" |
|
16 |
+ }, |
|
17 |
+ { |
|
18 |
+ "pageUrl" : "/rulebook/tasks", |
|
19 |
+ "slug" : "tasks", |
|
20 |
+ "title" : "Tasks & Validations", |
|
21 |
+ "tagline" : "How to automaticaly validate tasks" |
|
22 |
+ } |
|
23 |
+ ], |
|
24 |
+ "rest-api-v1" : |
|
25 |
+ [ |
|
26 |
+ { |
|
27 |
+ "pageUrl" : "/rest-api-v1/get-missions", |
|
28 |
+ "slug" : "get-missions", |
|
29 |
+ "title" : "Mission List", |
|
30 |
+ "endpoint" : { |
|
31 |
+ "type" : "GET", |
|
32 |
+ "base" : "/missions" |
|
33 |
+ }, |
|
34 |
+ "implemented" : true, |
|
35 |
+ "description" : "You can get a list of missions and their details using this endpoint. Its also possible to do pagination and use filters to fine grain the returned results.", |
|
36 |
+ "variables" : [ |
|
37 |
+ { |
|
38 |
+ "name" : "count", |
|
39 |
+ "description" : "The number of items in the list", |
|
40 |
+ "variable_type" : "number" |
|
41 |
+ }, |
|
42 |
+ { |
|
43 |
+ "name" : "page", |
|
44 |
+ "description" : "The page of items to be return. <i>Example: if count is 10 and page is 3, then the items 20-30 will be returned.</i>", |
|
45 |
+ "variable_type" : "number" |
|
46 |
+ }, |
|
47 |
+ { |
|
48 |
+ "name" : "status", |
|
49 |
+ "description" : "Filter for the missions status. Status can be <code>Planning</code>, <code>Launched</code>, <code>Completed</code>, <code>Failed</code> or <code>Canceled</code>", |
|
50 |
+ "variable_type" : "string" |
|
51 |
+ } |
|
52 |
+ ], |
|
53 |
+ "examples" : [ |
|
54 |
+ { |
|
55 |
+ "name" : "cURL", |
|
56 |
+ "language" : "bash", |
|
57 |
+ "code" : "views/snipets/bash/get-missions-example.html" |
|
58 |
+ }, |
|
59 |
+ { |
|
60 |
+ "name" : "jQuery", |
|
61 |
+ "language" : "javascript", |
|
62 |
+ "code" : "views/snipets/jquery/get-missions-example.html" |
|
63 |
+ } |
|
64 |
+ ] |
|
65 |
+ }, |
|
66 |
+ { |
|
67 |
+ "pageUrl" : "/rest-api-v1/get-mission", |
|
68 |
+ "slug" : "get-mission", |
|
69 |
+ "title" : "Mission Details", |
|
70 |
+ "endpoint" : { |
|
71 |
+ "type" : "GET", |
|
72 |
+ "base" : "/missions/:slug" |
|
73 |
+ }, |
|
74 |
+ "implemented" : true, |
|
75 |
+ "description" : "Returns detailed information about a mission, including lists of all agents, all tasks and recent activity.", |
|
76 |
+ "variables" : [ |
|
77 |
+ { |
|
78 |
+ "name" : "slug", |
|
79 |
+ "description" : "A unique shortname identifier for the mission.", |
|
80 |
+ "variable_type" : "string" |
|
81 |
+ } |
|
82 |
+ ], |
|
83 |
+ "examples" : [ |
|
84 |
+ { |
|
85 |
+ "name" : "cURL", |
|
86 |
+ "language" : "bash", |
|
87 |
+ "code" : "views/snipets/bash/get-mission-example.html" |
|
88 |
+ }, |
|
89 |
+ { |
|
90 |
+ "name" : "jQuery", |
|
91 |
+ "language" : "javascript", |
|
92 |
+ "code" : "views/snipets/jquery/get-mission-example.html" |
|
93 |
+ } |
|
94 |
+ ] |
|
95 |
+ }, |
|
96 |
+ { |
|
97 |
+ "pageUrl" : "/rest-api-v1/post-mission", |
|
98 |
+ "slug" : "post-mission", |
|
99 |
+ "title" : "Post Mission", |
|
100 |
+ "endpoint" : { |
|
101 |
+ "type" : "POST", |
|
102 |
+ "base" : "/missions" |
|
103 |
+ }, |
|
104 |
+ "description" : "Creates a new mission on the system and returns it back. A user token is needed for this request.", |
|
105 |
+ "variables" : [ |
|
106 |
+ { |
|
107 |
+ "name" : "slug", |
|
108 |
+ "description" : "A shortname without spaces used as the mission identifier and url. This needs to be unique.", |
|
109 |
+ "variable_type" : "string" |
|
110 |
+ }, |
|
111 |
+ { |
|
112 |
+ "name" : "title", |
|
113 |
+ "description" : "The mission's title or name", |
|
114 |
+ "variable_type" : "string" |
|
115 |
+ } |
|
116 |
+ ] |
|
117 |
+ }, |
|
118 |
+ { |
|
119 |
+ "pageUrl" : "/rest-api-v1/launch-mission", |
|
120 |
+ "slug" : "launch-mission", |
|
121 |
+ "title" : "Launch Mission", |
|
122 |
+ "endpoint" : { |
|
123 |
+ "type" : "POST", |
|
124 |
+ "base" : "/missions/:slug/launch" |
|
125 |
+ } |
|
126 |
+ }, |
|
127 |
+ { |
|
128 |
+ "pageUrl" : "/rest-api-v1/delete-mission", |
|
129 |
+ "slug" : "delete-mission", |
|
130 |
+ "title" : "Delete Mission", |
|
131 |
+ "endpoint" : { |
|
132 |
+ "type" : "DELETE", |
|
133 |
+ "base" : "/missions/:slug" |
|
134 |
+ } |
|
135 |
+ }, |
|
136 |
+ { |
|
137 |
+ "pageUrl" : "/rest-api-v1/get-mission-agents", |
|
138 |
+ "slug" : "get-mission-agents", |
|
139 |
+ "title" : "Mission Agent List", |
|
140 |
+ "endpoint" : { |
|
141 |
+ "type" : "GET", |
|
142 |
+ "base" : "/missions/:slug/agents" |
|
143 |
+ } |
|
144 |
+ }, |
|
145 |
+ { |
|
146 |
+ "pageUrl" : "/rest-api-v1/get-mission-agent", |
|
147 |
+ "slug" : "get-mission-agent", |
|
148 |
+ "title" : "Mission Agent Details", |
|
149 |
+ "endpoint" : { |
|
150 |
+ "type" : "GET", |
|
151 |
+ "base" : "/missions/:slug/agents/:slug" |
|
152 |
+ } |
|
153 |
+ }, |
|
154 |
+ { |
|
155 |
+ "pageUrl" : "/rest-api-v1/post-mission-agent", |
|
156 |
+ "slug" : "post-mission-agent", |
|
157 |
+ "title" : "Post Mission Agent", |
|
158 |
+ "endpoint" : { |
|
159 |
+ "type" : "POST", |
|
160 |
+ "base" : "/missions/:slug/agents" |
|
161 |
+ } |
|
162 |
+ }, |
|
163 |
+ { |
|
164 |
+ "pageUrl" : "/rest-api-v1/delete-mission-agent", |
|
165 |
+ "slug" : "delete-mission-agent", |
|
166 |
+ "title" : "Delete Mission Agent", |
|
167 |
+ "endpoint" : { |
|
168 |
+ "type" : "DELETE", |
|
169 |
+ "base" : "/missions/:slug/agents/:slug" |
|
170 |
+ } |
|
171 |
+ }, |
|
172 |
+ { |
|
173 |
+ "pageUrl" : "/rest-api-v1/get-mission-tasks", |
|
174 |
+ "slug" : "get-mission-tasks", |
|
175 |
+ "title" : "Mission Task List", |
|
176 |
+ "endpoint" : { |
|
177 |
+ "type" : "GET", |
|
178 |
+ "base" : "/missions/:slug/tasks" |
|
179 |
+ } |
|
180 |
+ }, |
|
181 |
+ { |
|
182 |
+ "pageUrl" : "/rest-api-v1/get-mission-task", |
|
183 |
+ "slug" : "get-mission-task", |
|
184 |
+ "title" : "Mission Task Details", |
|
185 |
+ "endpoint" : { |
|
186 |
+ "type" : "GET", |
|
187 |
+ "base" : "/missions/:slug/tasks/:id" |
|
188 |
+ } |
|
189 |
+ }, |
|
190 |
+ { |
|
191 |
+ "pageUrl" : "/rest-api-v1/mark-mission-task", |
|
192 |
+ "slug" : "mark-mission-task", |
|
193 |
+ "title" : "Mark Mission Task", |
|
194 |
+ "endpoint" : { |
|
195 |
+ "type" : "POST", |
|
196 |
+ "base" : "/missions/:slug/tasks/:id/mark" |
|
197 |
+ } |
|
198 |
+ }, |
|
199 |
+ { |
|
200 |
+ "pageUrl" : "/rest-api-v1/post-mission-task", |
|
201 |
+ "slug" : "post-mission-task", |
|
202 |
+ "title" : "Post Mission Task", |
|
203 |
+ "endpoint" : { |
|
204 |
+ "type" : "POST", |
|
205 |
+ "base" : "/missions/:slug/tasks" |
|
206 |
+ } |
|
207 |
+ }, |
|
208 |
+ { |
|
209 |
+ "pageUrl" : "/rest-api-v1/delete-mission-task", |
|
210 |
+ "slug" : "delete-mission-task", |
|
211 |
+ "title" : "Delete Mission Task", |
|
212 |
+ "endpoint" : { |
|
213 |
+ "type" : "DELETE", |
|
214 |
+ "base" : "/missions/:slug/tasks/:id" |
|
215 |
+ } |
|
216 |
+ } |
|
217 |
+ ], |
|
218 |
+ "realtime-api-v1" : |
|
219 |
+ [ |
|
220 |
+ { |
|
221 |
+ "pageUrl" : "/realtime-api-v1/realtime-mission-activity", |
|
222 |
+ "slug" : "realtime-mission-activity", |
|
223 |
+ "title" : "Mission Activity", |
|
224 |
+ "tagline" : "subscribe to a mission activity feed", |
|
225 |
+ "description" : "Subscribe to a missions activity feed for realtime updates. Activities includes updates on all public messages, agents, tasks, rewards and mission status.", |
|
226 |
+ "implemented" : true |
|
227 |
+ }, |
|
228 |
+ { |
|
229 |
+ "pageUrl" : "/realtime-api-v1/realtime-mission-agents", |
|
230 |
+ "slug" : "realtime-mission-agents", |
|
231 |
+ "title" : "Mission Agents Status", |
|
232 |
+ "tagline" : "Status off all agents on a mission", |
|
233 |
+ "description" : "Subscribe to get changes from agents status on a specific mission. Basically when a mission agent changes connection status (online/offline) or location a message is broadcasted.", |
|
234 |
+ "implemented" : false |
|
235 |
+ }, |
|
236 |
+ { |
|
237 |
+ "pageUrl" : "/realtime-api-v1/agent-status", |
|
238 |
+ "slug" : "agent-status", |
|
239 |
+ "title" : "Agent Status", |
|
240 |
+ "tagline" : "Agent online/offline status, position and stats", |
|
241 |
+ "implemented" : false |
|
242 |
+ }, |
|
243 |
+ { |
|
244 |
+ "pageUrl" : "/realtime-api-v1/realtime-user-notifications", |
|
245 |
+ "slug" : "realtime-user-notifications", |
|
246 |
+ "title" : "User Notifications", |
|
247 |
+ "tagline" : "Personal user notification feed", |
|
248 |
+ "implemented" : false |
|
249 |
+ } |
|
250 |
+ ] |
|
251 |
+} |
@@ -1,191 +0,0 @@ |
||
1 |
-[ |
|
2 |
- { |
|
3 |
- "pageUrl" : "/rest-api-v1/get-missions", |
|
4 |
- "slug" : "get-missions", |
|
5 |
- "title" : "Mission List", |
|
6 |
- "endpoint" : { |
|
7 |
- "type" : "GET", |
|
8 |
- "base" : "/missions" |
|
9 |
- }, |
|
10 |
- "description" : "You can get a list of missions and their details using this endpoint. Its also possible to do pagination and use filters to fine grain the returned results.", |
|
11 |
- "variables" : [ |
|
12 |
- { |
|
13 |
- "name" : "count", |
|
14 |
- "description" : "The number of items in the list", |
|
15 |
- "variable_type" : "number" |
|
16 |
- }, |
|
17 |
- { |
|
18 |
- "name" : "page", |
|
19 |
- "description" : "The page of items to be return. <i>Example: if count is 10 and page is 3, then the items 20-30 will be returned.</i>", |
|
20 |
- "variable_type" : "number" |
|
21 |
- }, |
|
22 |
- { |
|
23 |
- "name" : "status", |
|
24 |
- "description" : "Filter for the missions status. Status can be <code>Planning</code>, <code>Launched</code>, <code>Completed</code>, <code>Failed</code> or <code>Canceled</code>", |
|
25 |
- "variable_type" : "string" |
|
26 |
- } |
|
27 |
- ], |
|
28 |
- "examples" : [ |
|
29 |
- { |
|
30 |
- "name" : "cURL", |
|
31 |
- "language" : "bash", |
|
32 |
- "code" : "views/snipets/bash/get-missions-example.html" |
|
33 |
- }, |
|
34 |
- { |
|
35 |
- "name" : "jQuery", |
|
36 |
- "language" : "javascript", |
|
37 |
- "code" : "views/snipets/jquery/get-missions-example.html" |
|
38 |
- } |
|
39 |
- ] |
|
40 |
- }, |
|
41 |
- { |
|
42 |
- "pageUrl" : "/rest-api-v1/get-mission", |
|
43 |
- "slug" : "get-mission", |
|
44 |
- "title" : "Mission Details", |
|
45 |
- "endpoint" : { |
|
46 |
- "type" : "GET", |
|
47 |
- "base" : "/missions/:slug" |
|
48 |
- }, |
|
49 |
- "description" : "Returns detailed information about a mission, including lists of all agents, all tasks and recent activity.", |
|
50 |
- "variables" : [ |
|
51 |
- { |
|
52 |
- "name" : "slug", |
|
53 |
- "description" : "A unique shortname identifier for the mission.", |
|
54 |
- "variable_type" : "string" |
|
55 |
- } |
|
56 |
- ], |
|
57 |
- "examples" : [ |
|
58 |
- { |
|
59 |
- "name" : "cURL", |
|
60 |
- "language" : "bash", |
|
61 |
- "code" : "views/snipets/bash/get-mission-example.html" |
|
62 |
- }, |
|
63 |
- { |
|
64 |
- "name" : "jQuery", |
|
65 |
- "language" : "javascript", |
|
66 |
- "code" : "views/snipets/jquery/get-mission-example.html" |
|
67 |
- } |
|
68 |
- ] |
|
69 |
- }, |
|
70 |
- { |
|
71 |
- "pageUrl" : "/rest-api-v1/post-mission", |
|
72 |
- "slug" : "post-mission", |
|
73 |
- "title" : "Post Mission", |
|
74 |
- "endpoint" : { |
|
75 |
- "type" : "POST", |
|
76 |
- "base" : "/missions" |
|
77 |
- }, |
|
78 |
- "description" : "Creates a new mission on the system and returns it back. A user token is needed for this request.", |
|
79 |
- "variables" : [ |
|
80 |
- { |
|
81 |
- "name" : "slug", |
|
82 |
- "description" : "A shortname without spaces used as the mission identifier and url. This needs to be unique.", |
|
83 |
- "variable_type" : "string" |
|
84 |
- }, |
|
85 |
- { |
|
86 |
- "name" : "title", |
|
87 |
- "description" : "The mission's title or name", |
|
88 |
- "variable_type" : "string" |
|
89 |
- } |
|
90 |
- ] |
|
91 |
- }, |
|
92 |
- { |
|
93 |
- "pageUrl" : "/rest-api-v1/launch-mission", |
|
94 |
- "slug" : "launch-mission", |
|
95 |
- "title" : "Launch Mission", |
|
96 |
- "endpoint" : { |
|
97 |
- "type" : "POST", |
|
98 |
- "base" : "/missions/:slug/launch" |
|
99 |
- } |
|
100 |
- }, |
|
101 |
- { |
|
102 |
- "pageUrl" : "/rest-api-v1/delete-mission", |
|
103 |
- "slug" : "delete-mission", |
|
104 |
- "title" : "Delete Mission", |
|
105 |
- "endpoint" : { |
|
106 |
- "type" : "DELETE", |
|
107 |
- "base" : "/missions/:slug" |
|
108 |
- } |
|
109 |
- }, |
|
110 |
- { |
|
111 |
- "pageUrl" : "/rest-api-v1/get-mission-agents", |
|
112 |
- "slug" : "get-mission-agents", |
|
113 |
- "title" : "Mission Agent List", |
|
114 |
- "endpoint" : { |
|
115 |
- "type" : "GET", |
|
116 |
- "base" : "/missions/:slug/agents" |
|
117 |
- } |
|
118 |
- }, |
|
119 |
- { |
|
120 |
- "pageUrl" : "/rest-api-v1/get-mission-agent", |
|
121 |
- "slug" : "get-mission-agent", |
|
122 |
- "title" : "Mission Agent Details", |
|
123 |
- "endpoint" : { |
|
124 |
- "type" : "GET", |
|
125 |
- "base" : "/missions/:slug/agents/:slug" |
|
126 |
- } |
|
127 |
- }, |
|
128 |
- { |
|
129 |
- "pageUrl" : "/rest-api-v1/post-mission-agent", |
|
130 |
- "slug" : "post-mission-agent", |
|
131 |
- "title" : "Post Mission Agent", |
|
132 |
- "endpoint" : { |
|
133 |
- "type" : "POST", |
|
134 |
- "base" : "/missions/:slug/agents" |
|
135 |
- } |
|
136 |
- }, |
|
137 |
- { |
|
138 |
- "pageUrl" : "/rest-api-v1/delete-mission-agent", |
|
139 |
- "slug" : "delete-mission-agent", |
|
140 |
- "title" : "Delete Mission Agent", |
|
141 |
- "endpoint" : { |
|
142 |
- "type" : "DELETE", |
|
143 |
- "base" : "/missions/:slug/agents/:slug" |
|
144 |
- } |
|
145 |
- }, |
|
146 |
- { |
|
147 |
- "pageUrl" : "/rest-api-v1/get-mission-tasks", |
|
148 |
- "slug" : "get-mission-tasks", |
|
149 |
- "title" : "Mission Task List", |
|
150 |
- "endpoint" : { |
|
151 |
- "type" : "GET", |
|
152 |
- "base" : "/missions/:slug/tasks" |
|
153 |
- } |
|
154 |
- }, |
|
155 |
- { |
|
156 |
- "pageUrl" : "/rest-api-v1/get-mission-task", |
|
157 |
- "slug" : "get-mission-task", |
|
158 |
- "title" : "Mission Task Details", |
|
159 |
- "endpoint" : { |
|
160 |
- "type" : "GET", |
|
161 |
- "base" : "/missions/:slug/tasks/:id" |
|
162 |
- } |
|
163 |
- }, |
|
164 |
- { |
|
165 |
- "pageUrl" : "/rest-api-v1/mark-mission-task", |
|
166 |
- "slug" : "mark-mission-task", |
|
167 |
- "title" : "Mark Mission Task", |
|
168 |
- "endpoint" : { |
|
169 |
- "type" : "POST", |
|
170 |
- "base" : "/missions/:slug/tasks/:id/mark" |
|
171 |
- } |
|
172 |
- }, |
|
173 |
- { |
|
174 |
- "pageUrl" : "/rest-api-v1/post-mission-task", |
|
175 |
- "slug" : "post-mission-task", |
|
176 |
- "title" : "Post Mission Task", |
|
177 |
- "endpoint" : { |
|
178 |
- "type" : "POST", |
|
179 |
- "base" : "/missions/:slug/tasks" |
|
180 |
- } |
|
181 |
- }, |
|
182 |
- { |
|
183 |
- "pageUrl" : "/rest-api-v1/delete-mission-task", |
|
184 |
- "slug" : "delete-mission-task", |
|
185 |
- "title" : "Delete Mission Task", |
|
186 |
- "endpoint" : { |
|
187 |
- "type" : "DELETE", |
|
188 |
- "base" : "/missions/:slug/tasks/:id" |
|
189 |
- } |
|
190 |
- } |
|
191 |
-] |
@@ -0,0 +1,3 @@ |
||
1 |
+Welcome to the Avalanche Rulebook. The objective of this documentation is to explain, in depth, each part of the Avalanche Network mechanics. |
|
2 |
+ |
|
3 |
+The main building block of Avalanche are [missions](rulebook/missions). Each mission has [agents](rulebook/agents), [tasks](rulebook/tasks), [rewards](rulebook/rewards) and [objectives](rulebook/objectives). Each of this parts will be explained with details in the following pages. |
@@ -10,6 +10,7 @@ |
||
10 | 10 |
<!-- build:css(.) styles/vendor.css --> |
11 | 11 |
<!-- bower:css --> |
12 | 12 |
<link rel="stylesheet" href="bower_components/bootstrap/dist/css/bootstrap.css" /> |
13 |
+ <link rel="stylesheet" href="bower_components/ng-json-explorer/dist/angular-json-explorer.css" /> |
|
13 | 14 |
<!-- endbower --> |
14 | 15 |
<!-- endbuild --> |
15 | 16 |
<!-- build:css(.tmp) styles/main.css --> |
@@ -21,6 +22,7 @@ |
||
21 | 22 |
<!-- endbuild --> |
22 | 23 |
<link rel="stylesheet" href="styles/tomorrow-night-eighties.css"> |
23 | 24 |
<script src="scripts/libs/highlight.min.js"></script> |
25 |
+ <script src="scripts/libs/faye-browser.js"></script> |
|
24 | 26 |
|
25 | 27 |
</head> |
26 | 28 |
<body ng-app="avalancheDocsApp"> |
@@ -44,12 +46,13 @@ |
||
44 | 46 |
ga('send', 'pageview'); |
45 | 47 |
</script> |
46 | 48 |
--> |
47 |
- |
|
49 |
+ |
|
48 | 50 |
<!-- build:js(.) scripts/vendor.js --> |
49 | 51 |
<!-- bower:js --> |
50 | 52 |
<script src="bower_components/jquery/dist/jquery.js"></script> |
51 | 53 |
<script src="bower_components/angular/angular.js"></script> |
52 | 54 |
<script src="bower_components/bootstrap/dist/js/bootstrap.js"></script> |
55 |
+ <script src="bower_components/angularjs/angular.js"></script> |
|
53 | 56 |
<script src="bower_components/angular-animate/angular-animate.js"></script> |
54 | 57 |
<script src="bower_components/angular-cookies/angular-cookies.js"></script> |
55 | 58 |
<script src="bower_components/angular-resource/angular-resource.js"></script> |
@@ -62,6 +65,9 @@ |
||
62 | 65 |
<script src="bower_components/spin.js/spin.js"></script> |
63 | 66 |
<script src="bower_components/angular-spinner/angular-spinner.js"></script> |
64 | 67 |
<script src="bower_components/angular-ui-router/release/angular-ui-router.js"></script> |
68 |
+ <script src="bower_components/showdown/src/showdown.js"></script> |
|
69 |
+ <script src="bower_components/angular-markdown-directive/markdown.js"></script> |
|
70 |
+ <script src="bower_components/ng-json-explorer/dist/angular-json-explorer.js"></script> |
|
65 | 71 |
<!-- endbower --> |
66 | 72 |
<!-- endbuild --> |
67 | 73 |
|
@@ -70,9 +76,11 @@ |
||
70 | 76 |
<script src="scripts/controllers/getting-started-ctrl.js"></script> |
71 | 77 |
<script src="scripts/controllers/rulebook-ctrl.js"></script> |
72 | 78 |
<script src="scripts/controllers/rest-api-v1-ctrl.js"></script> |
79 |
+ <script src="scripts/controllers/realtime-api-v1-ctrl.js"></script> |
|
73 | 80 |
<script src="scripts/controllers/about.js"></script> |
74 | 81 |
<script src="scripts/services/oauth-service.js"></script> |
75 | 82 |
<script src="scripts/services/data-service.js"></script> |
83 |
+ <script src="scripts/services/realtime-service.js"></script> |
|
76 | 84 |
<script src="scripts/services/page-service.js"></script> |
77 | 85 |
<!-- endbuild --> |
78 | 86 |
|
@@ -19,8 +19,9 @@ angular |
||
19 | 19 |
'ngTouch', |
20 | 20 |
'ui.bootstrap', |
21 | 21 |
'hljs', |
22 |
- 'jsonFormatter', |
|
23 |
- 'angularSpinner' |
|
22 |
+ 'ngJsonExplorer', |
|
23 |
+ 'angularSpinner', |
|
24 |
+ 'btford.markdown' |
|
24 | 25 |
]) |
25 | 26 |
.config(['$locationProvider', '$stateProvider', '$urlRouterProvider', '$httpProvider', function($locationProvider, $stateProvider, $urlRouterProvider, $httpProvider) { |
26 | 27 |
// Configs |
@@ -41,19 +42,14 @@ angular |
||
41 | 42 |
controller: 'GettingStartedCtrl' |
42 | 43 |
}) |
43 | 44 |
.state('rulebook', { |
44 |
- url: "/rulebook", |
|
45 |
- templateUrl: 'views/rulebook/index.html', |
|
46 |
- controller: 'RulebookCtrl' |
|
47 |
- }) |
|
48 |
- .state('rulebook.intro', { |
|
49 |
- url: "/intro", |
|
50 |
- templateUrl: 'views/rulebook/introduction.html', |
|
51 |
- controller: 'AboutCtrl' |
|
52 |
- }) |
|
53 |
- .state('rulebook.mission', { |
|
54 |
- url: "/mission", |
|
55 |
- templateUrl: 'views/rulebook/mission.html', |
|
56 |
- controller: 'AboutCtrl' |
|
45 |
+ url: "/rulebook/:id", |
|
46 |
+ templateUrl: 'views/rulebook.html', |
|
47 |
+ controller: 'RulebookCtrl', |
|
48 |
+ resolve: { |
|
49 |
+ pageData: function($stateParams, PageService) { |
|
50 |
+ return PageService.find("rulebook", $stateParams.id); |
|
51 |
+ }, |
|
52 |
+ } |
|
57 | 53 |
}) |
58 | 54 |
.state('rest-api-v1', { |
59 | 55 |
url: "/rest-api-v1/:id", |
@@ -61,7 +57,17 @@ angular |
||
61 | 57 |
controller: 'RestAPIv1Ctrl', |
62 | 58 |
resolve: { |
63 | 59 |
pageData: function($stateParams, PageService) { |
64 |
- return PageService.find($stateParams.id); |
|
60 |
+ return PageService.find("rest-api-v1", $stateParams.id); |
|
61 |
+ }, |
|
62 |
+ } |
|
63 |
+ }) |
|
64 |
+ .state('realtime-api-v1', { |
|
65 |
+ url: "/realtime-api-v1/:id", |
|
66 |
+ templateUrl: "views/realtime-api-v1.html", |
|
67 |
+ controller: 'RealtimeAPIv1Ctrl', |
|
68 |
+ resolve: { |
|
69 |
+ pageData: function($stateParams, PageService) { |
|
70 |
+ return PageService.find("realtime-api-v1", $stateParams.id); |
|
65 | 71 |
}, |
66 | 72 |
} |
67 | 73 |
}) |
@@ -19,6 +19,10 @@ angular.module('avalancheDocsApp') |
||
19 | 19 |
$location.path($sanitize(before_login_page)).replace(); |
20 | 20 |
} |
21 | 21 |
|
22 |
+ $scope.navActivePage = function (viewLocation) { |
|
23 |
+ return viewLocation === "getting-started"; |
|
24 |
+ }; |
|
25 |
+ |
|
22 | 26 |
|
23 | 27 |
|
24 | 28 |
}]); |
@@ -0,0 +1,178 @@ |
||
1 |
+'use strict'; |
|
2 |
+ |
|
3 |
+/** |
|
4 |
+ * @ngdoc function |
|
5 |
+ * @name avalancheDocsApp.controller:AboutCtrl |
|
6 |
+ * @description |
|
7 |
+ * # AboutCtrl |
|
8 |
+ * Controller of the avalancheDocsApp |
|
9 |
+ */ |
|
10 |
+angular.module('avalancheDocsApp') |
|
11 |
+ .controller('RealtimeAPIv1Ctrl', ['$scope', '$location', '$cookies', '$window', '$rootScope', 'DataService', 'usSpinnerService', 'OAuthService', 'PageService', 'RealtimeService', function ($scope, $location, $cookies, $window, $rootScope, DataService, usSpinnerService, OAuthService, PageService, RealtimeService) { |
|
12 |
+ |
|
13 |
+ $scope.pageData = PageService.get(); |
|
14 |
+ $scope.pageList = PageService.all("realtime-api-v1"); |
|
15 |
+ //console.log("Loading page " + $scope.pageData.title) |
|
16 |
+ |
|
17 |
+ $rootScope.$on('get-pages:finished', function() { |
|
18 |
+ if(!$scope.$$phase) { |
|
19 |
+ $scope.$apply(function(){ |
|
20 |
+ $scope.pageData = PageService.get(); |
|
21 |
+ }); |
|
22 |
+ } else { |
|
23 |
+ $scope.pageData = PageService.get(); |
|
24 |
+ } |
|
25 |
+ }); |
|
26 |
+ |
|
27 |
+ $scope.navActivePage = function (viewLocation) { |
|
28 |
+ return viewLocation === "realtime-api-v1"; |
|
29 |
+ }; |
|
30 |
+ |
|
31 |
+ $scope.isActive = function (viewLocation) { |
|
32 |
+ return viewLocation === $location.path(); |
|
33 |
+ }; |
|
34 |
+ |
|
35 |
+ $scope.token = $cookies.get('avalanche-docs-token'); |
|
36 |
+ if($scope.token == undefined) { |
|
37 |
+ $scope.token = "none"; |
|
38 |
+ $scope.hideToken = true; |
|
39 |
+ } |
|
40 |
+ |
|
41 |
+ $scope.authorizeUser = function(){ |
|
42 |
+ console.log($location.url()); |
|
43 |
+ $cookies.put('avalanche_docs_before_login_page', $location.url()); |
|
44 |
+ $window.location.href = 'http://localhost:5000/oauth/authorize?client_id=d514f58c234d69ce1405f00dbef842bd785c09201b35a746d87306f5e69fd02b&redirect_uri=http://localhost:9000/&response_type=code'; |
|
45 |
+ } |
|
46 |
+ |
|
47 |
+ |
|
48 |
+ // Initialize |
|
49 |
+ $scope.response = {}; |
|
50 |
+ $scope.hideResult = true; |
|
51 |
+ $scope.tokens = { availableOptions : [], selectedOption: []}; |
|
52 |
+ $scope.tokens.availableOptions[0] = 'none'; |
|
53 |
+ $scope.tokens.selectedToken = $scope.tokens.availableOptions[0]; |
|
54 |
+ $scope.data = {} |
|
55 |
+ if(OAuthService.getToken() != "" && OAuthService.getToken() != undefined){ |
|
56 |
+ $scope.tokens.availableOptions[1] = OAuthService.getToken(); |
|
57 |
+ $scope.tokens.selectedToken = $scope.tokens.availableOptions[1]; |
|
58 |
+ } |
|
59 |
+ DataService.getResponse() |
|
60 |
+ |
|
61 |
+ // API Calls |
|
62 |
+ $scope.getData = function(url){ |
|
63 |
+ usSpinnerService.spin('spinner-1'); |
|
64 |
+ DataService.get(url, $scope.processInputs(), $scope.tokens.selectedToken); |
|
65 |
+ } |
|
66 |
+ $scope.postData = function(url){ |
|
67 |
+ usSpinnerService.spin('spinner-1'); |
|
68 |
+ DataService.post(url, $scope.processInputs(), $scope.data, $scope.tokens.selectedToken); |
|
69 |
+ } |
|
70 |
+ |
|
71 |
+ $scope.processInputs = function(){ |
|
72 |
+ var inputs = []; |
|
73 |
+ for (var i = 0; i < $scope.pageData.variables.length; i++) { |
|
74 |
+ var field = $scope.pageData.variables[i].name; |
|
75 |
+ inputs[field] = $scope.pageData.variables[i].value; |
|
76 |
+ } |
|
77 |
+ return inputs; |
|
78 |
+ } |
|
79 |
+ |
|
80 |
+ |
|
81 |
+ $scope.hideResponse = function() { |
|
82 |
+ $scope.hideResult = true; |
|
83 |
+ } |
|
84 |
+ $rootScope.$on('get-data:finished', function() { |
|
85 |
+ if(!$scope.$$phase) { |
|
86 |
+ $scope.$apply(function(){ |
|
87 |
+ $scope.response = DataService.getResponse(); |
|
88 |
+ $scope.hideResult = false |
|
89 |
+ }); |
|
90 |
+ } else { |
|
91 |
+ $scope.response = DataService.getResponse(); |
|
92 |
+ $scope.hideResult = false |
|
93 |
+ } |
|
94 |
+ usSpinnerService.stop('spinner-1'); |
|
95 |
+ }); |
|
96 |
+ |
|
97 |
+ $rootScope.$on('auth:success', function() { |
|
98 |
+ if(!$scope.$$phase) { |
|
99 |
+ $scope.$apply(function(){ |
|
100 |
+ $scope.token[1] = OAuthService.getToken(); |
|
101 |
+ $scope.selectedToken = $scope.tokens.availableOptions[1]; |
|
102 |
+ }); |
|
103 |
+ } else { |
|
104 |
+ $scope.token[1] = OAuthService.getToken(); |
|
105 |
+ $scope.selectedToken = $scope.tokens.availableOptions[1]; |
|
106 |
+ } |
|
107 |
+ }); |
|
108 |
+ |
|
109 |
+ $scope.subscribing = false; |
|
110 |
+ $scope.messages = []; |
|
111 |
+ $scope.messages.add = function(message) { |
|
112 |
+ this.push(message); |
|
113 |
+ } |
|
114 |
+ |
|
115 |
+ // Listen to data coming from the server via Faye |
|
116 |
+ $scope.subscribe = function(url){ |
|
117 |
+ if($scope.has_disconected == true){ |
|
118 |
+ RealtimeService.connect(); |
|
119 |
+ } |
|
120 |
+ RealtimeService.subscribe(url, function(msg) { |
|
121 |
+ $scope.$apply(function() { |
|
122 |
+ $scope.messages.add(msg); |
|
123 |
+ console.log(msg); |
|
124 |
+ }); |
|
125 |
+ }); |
|
126 |
+ } |
|
127 |
+ |
|
128 |
+ |
|
129 |
+ // Post the data to the server and have it send to us |
|
130 |
+ $scope.sendServer = function() { |
|
131 |
+ $http.post('/', { foo: 'asd', message: $scope.message }) |
|
132 |
+ .success(function() { |
|
133 |
+ $scope.message = ''; |
|
134 |
+ }) |
|
135 |
+ .error(function(data, status) { |
|
136 |
+ $scope.messages.add('error', "Error doing POST to server: " + status); |
|
137 |
+ }); |
|
138 |
+ }; |
|
139 |
+ |
|
140 |
+ // Send data to server via Faye |
|
141 |
+ $scope.sendClient = function() { |
|
142 |
+ Faye.publish('/fromclient', $scope.message); |
|
143 |
+ $scope.messages.add('outgoing', $scope.message); |
|
144 |
+ $scope.message = ''; |
|
145 |
+ }; |
|
146 |
+ |
|
147 |
+ // Misc |
|
148 |
+ $rootScope.$on('realtime:offline', function() { |
|
149 |
+ if(!$scope.$$phase) { |
|
150 |
+ $scope.$apply(function(){ |
|
151 |
+ $scope.connection_status = "<div class=\"circle-error\"></div> Disconected"; |
|
152 |
+ $scope.has_connection = false; |
|
153 |
+ }) |
|
154 |
+ } else { |
|
155 |
+ $scope.connection_status = "<div class=\"circle-error\"></div> Disconected"; |
|
156 |
+ $scope.has_connection = false; |
|
157 |
+ } |
|
158 |
+ }); |
|
159 |
+ $rootScope.$on('realtime:online', function() { |
|
160 |
+ if(!$scope.$$phase) { |
|
161 |
+ $scope.$apply(function(){ |
|
162 |
+ $scope.connection_status = "<div class=\"circle-ok\"></div> Connected"; |
|
163 |
+ $scope.has_connection = true; |
|
164 |
+ $scope.subscribing = true; |
|
165 |
+ }) |
|
166 |
+ } else { |
|
167 |
+ $scope.connection_status = "<div class=\"circle-ok\"></div> Connected"; |
|
168 |
+ $scope.has_connection = true; |
|
169 |
+ $scope.subscribing = true; |
|
170 |
+ } |
|
171 |
+ }); |
|
172 |
+ $scope.disconect = function() { |
|
173 |
+ RealtimeService.disconect(); |
|
174 |
+ $scope.has_disconected = true; |
|
175 |
+ } |
|
176 |
+ |
|
177 |
+ |
|
178 |
+ }]); |
@@ -11,9 +11,22 @@ angular.module('avalancheDocsApp') |
||
11 | 11 |
.controller('RestAPIv1Ctrl', ['$scope', '$location', '$cookies', '$window', '$rootScope', 'DataService', 'usSpinnerService', 'OAuthService', 'PageService', function ($scope, $location, $cookies, $window, $rootScope, DataService, usSpinnerService, OAuthService, PageService) { |
12 | 12 |
|
13 | 13 |
$scope.pageData = PageService.get(); |
14 |
- $scope.pageList = PageService.all(); |
|
15 |
- console.log("Loading page " + $scope.pageData.title) |
|
14 |
+ $scope.pageList = PageService.all("rest-api-v1"); |
|
15 |
+ //console.log("Loading page " + $scope.pageData.title) |
|
16 | 16 |
|
17 |
+ $rootScope.$on('get-pages:finished', function() { |
|
18 |
+ if(!$scope.$$phase) { |
|
19 |
+ $scope.$apply(function(){ |
|
20 |
+ $scope.pageData = PageService.get(); |
|
21 |
+ }); |
|
22 |
+ } else { |
|
23 |
+ $scope.pageData = PageService.get(); |
|
24 |
+ } |
|
25 |
+ }); |
|
26 |
+ |
|
27 |
+ $scope.navActivePage = function (viewLocation) { |
|
28 |
+ return viewLocation === "rest-api-v1"; |
|
29 |
+ }; |
|
17 | 30 |
|
18 | 31 |
$scope.isActive = function (viewLocation) { |
19 | 32 |
return viewLocation === $location.path(); |
@@ -8,7 +8,26 @@ |
||
8 | 8 |
* Controller of the avalancheDocsApp |
9 | 9 |
*/ |
10 | 10 |
angular.module('avalancheDocsApp') |
11 |
- .controller('RulebookCtrl', ['$scope', '$location', function ($scope, $location) { |
|
11 |
+ .controller('RulebookCtrl', ['$scope', '$location', 'PageService', '$rootScope', function ($scope, $location, PageService, $rootScope) { |
|
12 |
+ |
|
13 |
+ $scope.pageData = PageService.get(); |
|
14 |
+ $scope.pageList = PageService.all("rulebook"); |
|
15 |
+ //console.log("Loading page " + $scope.pageData.title) |
|
16 |
+ |
|
17 |
+ $rootScope.$on('get-pages:finished', function() { |
|
18 |
+ if(!$scope.$$phase) { |
|
19 |
+ $scope.$apply(function(){ |
|
20 |
+ $scope.pageData = PageService.get(); |
|
21 |
+ }); |
|
22 |
+ } else { |
|
23 |
+ $scope.pageData = PageService.get(); |
|
24 |
+ } |
|
25 |
+ }); |
|
26 |
+ |
|
27 |
+ $scope.navActivePage = function (viewLocation) { |
|
28 |
+ return viewLocation === "rulebook"; |
|
29 |
+ }; |
|
30 |
+ |
|
12 | 31 |
$scope.isActive = function (viewLocation) { |
13 | 32 |
return viewLocation === $location.path(); |
14 | 33 |
}; |
@@ -0,0 +1,2765 @@ |
||
1 |
+(function() { |
|
2 |
+'use strict'; |
|
3 |
+ |
|
4 |
+var Faye = { |
|
5 |
+ VERSION: '1.1.1', |
|
6 |
+ |
|
7 |
+ BAYEUX_VERSION: '1.0', |
|
8 |
+ ID_LENGTH: 160, |
|
9 |
+ JSONP_CALLBACK: 'jsonpcallback', |
|
10 |
+ CONNECTION_TYPES: ['long-polling', 'cross-origin-long-polling', 'callback-polling', 'websocket', 'eventsource', 'in-process'], |
|
11 |
+ |
|
12 |
+ MANDATORY_CONNECTION_TYPES: ['long-polling', 'callback-polling', 'in-process'], |
|
13 |
+ |
|
14 |
+ ENV: (typeof window !== 'undefined') ? window : global, |
|
15 |
+ |
|
16 |
+ extend: function(dest, source, overwrite) { |
|
17 |
+ if (!source) return dest; |
|
18 |
+ for (var key in source) { |
|
19 |
+ if (!source.hasOwnProperty(key)) continue; |
|
20 |
+ if (dest.hasOwnProperty(key) && overwrite === false) continue; |
|
21 |
+ if (dest[key] !== source[key]) |
|
22 |
+ dest[key] = source[key]; |
|
23 |
+ } |
|
24 |
+ return dest; |
|
25 |
+ }, |
|
26 |
+ |
|
27 |
+ random: function(bitlength) { |
|
28 |
+ bitlength = bitlength || this.ID_LENGTH; |
|
29 |
+ var maxLength = Math.ceil(bitlength * Math.log(2) / Math.log(36)); |
|
30 |
+ var string = csprng(bitlength, 36); |
|
31 |
+ while (string.length < maxLength) string = '0' + string; |
|
32 |
+ return string; |
|
33 |
+ }, |
|
34 |
+ |
|
35 |
+ validateOptions: function(options, validKeys) { |
|
36 |
+ for (var key in options) { |
|
37 |
+ if (this.indexOf(validKeys, key) < 0) |
|
38 |
+ throw new Error('Unrecognized option: ' + key); |
|
39 |
+ } |
|
40 |
+ }, |
|
41 |
+ |
|
42 |
+ clientIdFromMessages: function(messages) { |
|
43 |
+ var connect = this.filter([].concat(messages), function(message) { |
|
44 |
+ return message.channel === '/meta/connect'; |
|
45 |
+ }); |
|
46 |
+ return connect[0] && connect[0].clientId; |
|
47 |
+ }, |
|
48 |
+ |
|
49 |
+ copyObject: function(object) { |
|
50 |
+ var clone, i, key; |
|
51 |
+ if (object instanceof Array) { |
|
52 |
+ clone = []; |
|
53 |
+ i = object.length; |
|
54 |
+ while (i--) clone[i] = Faye.copyObject(object[i]); |
|
55 |
+ return clone; |
|
56 |
+ } else if (typeof object === 'object') { |
|
57 |
+ clone = (object === null) ? null : {}; |
|
58 |
+ for (key in object) clone[key] = Faye.copyObject(object[key]); |
|
59 |
+ return clone; |
|
60 |
+ } else { |
|
61 |
+ return object; |
|
62 |
+ } |
|
63 |
+ }, |
|
64 |
+ |
|
65 |
+ commonElement: function(lista, listb) { |
|
66 |
+ for (var i = 0, n = lista.length; i < n; i++) { |
|
67 |
+ if (this.indexOf(listb, lista[i]) !== -1) |
|
68 |
+ return lista[i]; |
|
69 |
+ } |
|
70 |
+ return null; |
|
71 |
+ }, |
|
72 |
+ |
|
73 |
+ indexOf: function(list, needle) { |
|
74 |
+ if (list.indexOf) return list.indexOf(needle); |
|
75 |
+ |
|
76 |
+ for (var i = 0, n = list.length; i < n; i++) { |
|
77 |
+ if (list[i] === needle) return i; |
|
78 |
+ } |
|
79 |
+ return -1; |
|
80 |
+ }, |
|
81 |
+ |
|
82 |
+ map: function(object, callback, context) { |
|
83 |
+ if (object.map) return object.map(callback, context); |
|
84 |
+ var result = []; |
|
85 |
+ |
|
86 |
+ if (object instanceof Array) { |
|
87 |
+ for (var i = 0, n = object.length; i < n; i++) { |
|
88 |
+ result.push(callback.call(context || null, object[i], i)); |
|
89 |
+ } |
|
90 |
+ } else { |
|
91 |
+ for (var key in object) { |
|
92 |
+ if (!object.hasOwnProperty(key)) continue; |
|
93 |
+ result.push(callback.call(context || null, key, object[key])); |
|
94 |
+ } |
|
95 |
+ } |
|
96 |
+ return result; |
|
97 |
+ }, |
|
98 |
+ |
|
99 |
+ filter: function(array, callback, context) { |
|
100 |
+ if (array.filter) return array.filter(callback, context); |
|
101 |
+ var result = []; |
|
102 |
+ for (var i = 0, n = array.length; i < n; i++) { |
|
103 |
+ if (callback.call(context || null, array[i], i)) |
|
104 |
+ result.push(array[i]); |
|
105 |
+ } |
|
106 |
+ return result; |
|
107 |
+ }, |
|
108 |
+ |
|
109 |
+ asyncEach: function(list, iterator, callback, context) { |
|
110 |
+ var n = list.length, |
|
111 |
+ i = -1, |
|
112 |
+ calls = 0, |
|
113 |
+ looping = false; |
|
114 |
+ |
|
115 |
+ var iterate = function() { |
|
116 |
+ calls -= 1; |
|
117 |
+ i += 1; |
|
118 |
+ if (i === n) return callback && callback.call(context); |
|
119 |
+ iterator(list[i], resume); |
|
120 |
+ }; |
|
121 |
+ |
|
122 |
+ var loop = function() { |
|
123 |
+ if (looping) return; |
|
124 |
+ looping = true; |
|
125 |
+ while (calls > 0) iterate(); |
|
126 |
+ looping = false; |
|
127 |
+ }; |
|
128 |
+ |
|
129 |
+ var resume = function() { |
|
130 |
+ calls += 1; |
|
131 |
+ loop(); |
|
132 |
+ }; |
|
133 |
+ resume(); |
|
134 |
+ }, |
|
135 |
+ |
|
136 |
+ // http://assanka.net/content/tech/2009/09/02/json2-js-vs-prototype/ |
|
137 |
+ toJSON: function(object) { |
|
138 |
+ if (!this.stringify) return JSON.stringify(object); |
|
139 |
+ |
|
140 |
+ return this.stringify(object, function(key, value) { |
|
141 |
+ return (this[key] instanceof Array) ? this[key] : value; |
|
142 |
+ }); |
|
143 |
+ } |
|
144 |
+}; |
|
145 |
+ |
|
146 |
+if (typeof module !== 'undefined') |
|
147 |
+ module.exports = Faye; |
|
148 |
+else if (typeof window !== 'undefined') |
|
149 |
+ window.Faye = Faye; |
|
150 |
+ |
|
151 |
+Faye.Class = function(parent, methods) { |
|
152 |
+ if (typeof parent !== 'function') { |
|
153 |
+ methods = parent; |
|
154 |
+ parent = Object; |
|
155 |
+ } |
|
156 |
+ |
|
157 |
+ var klass = function() { |
|
158 |
+ if (!this.initialize) return this; |
|
159 |
+ return this.initialize.apply(this, arguments) || this; |
|
160 |
+ }; |
|
161 |
+ |
|
162 |
+ var bridge = function() {}; |
|
163 |
+ bridge.prototype = parent.prototype; |
|
164 |
+ |
|
165 |
+ klass.prototype = new bridge(); |
|
166 |
+ Faye.extend(klass.prototype, methods); |
|
167 |
+ |
|
168 |
+ return klass; |
|
169 |
+}; |
|
170 |
+ |
|
171 |
+(function() { |
|
172 |
+var EventEmitter = Faye.EventEmitter = function() {}; |
|
173 |
+ |
|
174 |
+/* |
|
175 |
+Copyright Joyent, Inc. and other Node contributors. All rights reserved. |
|
176 |
+Permission is hereby granted, free of charge, to any person obtaining a copy of |
|
177 |
+this software and associated documentation files (the "Software"), to deal in |
|
178 |
+the Software without restriction, including without limitation the rights to |
|
179 |
+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies |
|
180 |
+of the Software, and to permit persons to whom the Software is furnished to do |
|
181 |
+so, subject to the following conditions: |
|
182 |
+ |
|
183 |
+The above copyright notice and this permission notice shall be included in all |
|
184 |
+copies or substantial portions of the Software. |
|
185 |
+ |
|
186 |
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
|
187 |
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
|
188 |
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
|
189 |
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
|
190 |
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
|
191 |
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE |
|
192 |
+SOFTWARE. |
|
193 |
+*/ |
|
194 |
+ |
|
195 |
+var isArray = typeof Array.isArray === 'function' |
|
196 |
+ ? Array.isArray |
|
197 |
+ : function (xs) { |
|
198 |
+ return Object.prototype.toString.call(xs) === '[object Array]' |
|
199 |
+ } |
|
200 |
+; |
|
201 |
+function indexOf (xs, x) { |
|
202 |
+ if (xs.indexOf) return xs.indexOf(x); |
|
203 |
+ for (var i = 0; i < xs.length; i++) { |
|
204 |
+ if (x === xs[i]) return i; |
|
205 |
+ } |
|
206 |
+ return -1; |
|
207 |
+} |
|
208 |
+ |
|
209 |
+ |
|
210 |
+EventEmitter.prototype.emit = function(type) { |
|
211 |
+ // If there is no 'error' event listener then throw. |
|
212 |
+ if (type === 'error') { |
|
213 |
+ if (!this._events || !this._events.error || |
|
214 |
+ (isArray(this._events.error) && !this._events.error.length)) |
|
215 |
+ { |
|
216 |
+ if (arguments[1] instanceof Error) { |
|
217 |
+ throw arguments[1]; // Unhandled 'error' event |
|
218 |
+ } else { |
|
219 |
+ throw new Error("Uncaught, unspecified 'error' event."); |
|
220 |
+ } |
|
221 |
+ return false; |
|
222 |
+ } |
|
223 |
+ } |
|
224 |
+ |
|
225 |
+ if (!this._events) return false; |
|
226 |
+ var handler = this._events[type]; |
|
227 |
+ if (!handler) return false; |
|
228 |
+ |
|
229 |
+ if (typeof handler == 'function') { |
|
230 |
+ switch (arguments.length) { |
|
231 |
+ // fast cases |
|
232 |
+ case 1: |
|
233 |
+ handler.call(this); |
|
234 |
+ break; |
|
235 |
+ case 2: |
|
236 |
+ handler.call(this, arguments[1]); |
|
237 |
+ break; |
|
238 |
+ case 3: |
|
239 |
+ handler.call(this, arguments[1], arguments[2]); |
|
240 |
+ break; |
|
241 |
+ // slower |
|
242 |
+ default: |
|
243 |
+ var args = Array.prototype.slice.call(arguments, 1); |
|
244 |
+ handler.apply(this, args); |
|
245 |
+ } |
|
246 |
+ return true; |
|
247 |
+ |
|
248 |
+ } else if (isArray(handler)) { |
|
249 |
+ var args = Array.prototype.slice.call(arguments, 1); |
|
250 |
+ |
|
251 |
+ var listeners = handler.slice(); |
|
252 |
+ for (var i = 0, l = listeners.length; i < l; i++) { |
|
253 |
+ listeners[i].apply(this, args); |
|
254 |
+ } |
|
255 |
+ return true; |
|
256 |
+ |
|
257 |
+ } else { |
|
258 |
+ return false; |
|
259 |
+ } |
|
260 |
+}; |
|
261 |
+ |
|
262 |
+// EventEmitter is defined in src/node_events.cc |
|
263 |
+// EventEmitter.prototype.emit() is also defined there. |
|
264 |
+EventEmitter.prototype.addListener = function(type, listener) { |
|
265 |
+ if ('function' !== typeof listener) { |
|
266 |
+ throw new Error('addListener only takes instances of Function'); |
|
267 |
+ } |
|
268 |
+ |
|
269 |
+ if (!this._events) this._events = {}; |
|
270 |
+ |
|
271 |
+ // To avoid recursion in the case that type == "newListeners"! Before |
|
272 |
+ // adding it to the listeners, first emit "newListeners". |
|
273 |
+ this.emit('newListener', type, listener); |
|
274 |
+ |
|
275 |
+ if (!this._events[type]) { |
|
276 |
+ // Optimize the case of one listener. Don't need the extra array object. |
|
277 |
+ this._events[type] = listener; |
|
278 |
+ } else if (isArray(this._events[type])) { |
|
279 |
+ // If we've already got an array, just append. |
|
280 |
+ this._events[type].push(listener); |
|
281 |
+ } else { |
|
282 |
+ // Adding the second element, need to change to array. |
|
283 |
+ this._events[type] = [this._events[type], listener]; |
|
284 |
+ } |
|
285 |
+ |
|
286 |
+ return this; |
|
287 |
+}; |
|
288 |
+ |
|
289 |
+EventEmitter.prototype.on = EventEmitter.prototype.addListener; |
|
290 |
+ |
|
291 |
+EventEmitter.prototype.once = function(type, listener) { |
|
292 |
+ var self = this; |
|
293 |
+ self.on(type, function g() { |
|
294 |
+ self.removeListener(type, g); |
|
295 |
+ listener.apply(this, arguments); |
|
296 |
+ }); |
|
297 |
+ |
|
298 |
+ return this; |
|
299 |
+}; |
|
300 |
+ |
|
301 |
+EventEmitter.prototype.removeListener = function(type, listener) { |
|
302 |
+ if ('function' !== typeof listener) { |
|
303 |
+ throw new Error('removeListener only takes instances of Function'); |
|
304 |
+ } |
|
305 |
+ |
|
306 |
+ // does not use listeners(), so no side effect of creating _events[type] |
|
307 |
+ if (!this._events || !this._events[type]) return this; |
|
308 |
+ |
|
309 |
+ var list = this._events[type]; |
|
310 |
+ |
|
311 |
+ if (isArray(list)) { |
|
312 |
+ var i = indexOf(list, listener); |
|
313 |
+ if (i < 0) return this; |
|
314 |
+ list.splice(i, 1); |
|
315 |
+ if (list.length == 0) |
|
316 |
+ delete this._events[type]; |
|
317 |
+ } else if (this._events[type] === listener) { |
|
318 |
+ delete this._events[type]; |
|
319 |
+ } |
|
320 |
+ |
|
321 |
+ return this; |
|
322 |
+}; |
|
323 |
+ |
|
324 |
+EventEmitter.prototype.removeAllListeners = function(type) { |
|
325 |
+ if (arguments.length === 0) { |
|
326 |
+ this._events = {}; |
|
327 |
+ return this; |
|
328 |
+ } |
|
329 |
+ |
|
330 |
+ // does not use listeners(), so no side effect of creating _events[type] |
|
331 |
+ if (type && this._events && this._events[type]) this._events[type] = null; |
|
332 |
+ return this; |
|
333 |
+}; |
|
334 |
+ |
|
335 |
+EventEmitter.prototype.listeners = function(type) { |
|
336 |
+ if (!this._events) this._events = {}; |
|
337 |
+ if (!this._events[type]) this._events[type] = []; |
|
338 |
+ if (!isArray(this._events[type])) { |
|
339 |
+ this._events[type] = [this._events[type]]; |
|
340 |
+ } |
|
341 |
+ return this._events[type]; |
|
342 |
+}; |
|
343 |
+ |
|
344 |
+})(); |
|
345 |
+ |
|
346 |
+Faye.Namespace = Faye.Class({ |
|
347 |
+ initialize: function() { |
|
348 |
+ this._used = {}; |
|
349 |
+ }, |
|
350 |
+ |
|
351 |
+ exists: function(id) { |
|
352 |
+ return this._used.hasOwnProperty(id); |
|
353 |
+ }, |
|
354 |
+ |
|
355 |
+ generate: function() { |
|
356 |
+ var name = Faye.random(); |
|
357 |
+ while (this._used.hasOwnProperty(name)) |
|
358 |
+ name = Faye.random(); |
|
359 |
+ return this._used[name] = name; |
|
360 |
+ }, |
|
361 |
+ |
|
362 |
+ release: function(id) { |
|
363 |
+ delete this._used[id]; |
|
364 |
+ } |
|
365 |
+}); |
|
366 |
+ |
|
367 |
+(function() { |
|
368 |
+'use strict'; |
|
369 |
+ |
|
370 |
+var timeout = setTimeout, defer; |
|
371 |
+ |
|
372 |
+if (typeof setImmediate === 'function') |
|
373 |
+ defer = function(fn) { setImmediate(fn) }; |
|
374 |
+else if (typeof process === 'object' && process.nextTick) |
|
375 |
+ defer = function(fn) { process.nextTick(fn) }; |
|
376 |
+else |
|
377 |
+ defer = function(fn) { timeout(fn, 0) }; |
|
378 |
+ |
|
379 |
+var PENDING = 0, |
|
380 |
+ FULFILLED = 1, |
|
381 |
+ REJECTED = 2; |
|
382 |
+ |
|
383 |
+var RETURN = function(x) { return x }, |
|
384 |
+ THROW = function(x) { throw x }; |
|
385 |
+ |
|
386 |
+var Promise = function(task) { |
|
387 |
+ this._state = PENDING; |
|
388 |
+ this._onFulfilled = []; |
|
389 |
+ this._onRejected = []; |
|
390 |
+ |
|
391 |
+ if (typeof task !== 'function') return; |
|
392 |
+ var self = this; |
|
393 |
+ |
|
394 |
+ task(function(value) { fulfill(self, value) }, |
|
395 |
+ function(reason) { reject(self, reason) }); |
|
396 |
+}; |
|
397 |
+ |
|
398 |
+Promise.prototype.then = function(onFulfilled, onRejected) { |
|
399 |
+ var next = new Promise(); |
|
400 |
+ registerOnFulfilled(this, onFulfilled, next); |
|
401 |
+ registerOnRejected(this, onRejected, next); |
|
402 |
+ return next; |
|
403 |
+}; |
|
404 |
+ |
|
405 |
+var registerOnFulfilled = function(promise, onFulfilled, next) { |
|
406 |
+ if (typeof onFulfilled !== 'function') onFulfilled = RETURN; |
|
407 |
+ var handler = function(value) { invoke(onFulfilled, value, next) }; |
|
408 |
+ |
|
409 |
+ if (promise._state === PENDING) { |
|
410 |
+ promise._onFulfilled.push(handler); |
|
411 |
+ } else if (promise._state === FULFILLED) { |
|
412 |
+ handler(promise._value); |
|
413 |
+ } |
|
414 |
+}; |
|
415 |
+ |
|
416 |
+var registerOnRejected = function(promise, onRejected, next) { |
|
417 |
+ if (typeof onRejected !== 'function') onRejected = THROW; |
|
418 |
+ var handler = function(reason) { invoke(onRejected, reason, next) }; |
|
419 |
+ |
|
420 |
+ if (promise._state === PENDING) { |
|
421 |
+ promise._onRejected.push(handler); |
|
422 |
+ } else if (promise._state === REJECTED) { |
|
423 |
+ handler(promise._reason); |
|
424 |
+ } |
|
425 |
+}; |
|
426 |
+ |
|
427 |
+var invoke = function(fn, value, next) { |
|
428 |
+ defer(function() { _invoke(fn, value, next) }); |
|
429 |
+}; |
|
430 |
+ |
|
431 |
+var _invoke = function(fn, value, next) { |
|
432 |
+ var outcome; |
|
433 |
+ |
|
434 |
+ try { |
|
435 |
+ outcome = fn(value); |
|
436 |
+ } catch (error) { |
|
437 |
+ return reject(next, error); |
|
438 |
+ } |
|
439 |
+ |
|
440 |
+ if (outcome === next) { |
|
441 |
+ reject(next, new TypeError('Recursive promise chain detected')); |
|
442 |
+ } else { |
|
443 |
+ fulfill(next, outcome); |
|
444 |
+ } |
|
445 |
+}; |
|
446 |
+ |
|
447 |
+var fulfill = Promise.fulfill = Promise.resolve = function(promise, value) { |
|
448 |
+ var called = false, type, then; |
|
449 |
+ |
|
450 |
+ try { |
|
451 |
+ type = typeof value; |
|
452 |
+ then = value !== null && (type === 'function' || type === 'object') && value.then; |
|
453 |
+ |
|
454 |
+ if (typeof then !== 'function') return _fulfill(promise, value); |
|
455 |
+ |
|
456 |
+ then.call(value, function(v) { |
|
457 |
+ if (!(called ^ (called = true))) return; |
|
458 |
+ fulfill(promise, v); |
|
459 |
+ }, function(r) { |
|
460 |
+ if (!(called ^ (called = true))) return; |
|
461 |
+ reject(promise, r); |
|
462 |
+ }); |
|
463 |
+ } catch (error) { |
|
464 |
+ if (!(called ^ (called = true))) return; |
|
465 |
+ reject(promise, error); |
|
466 |
+ } |
|
467 |
+}; |
|
468 |
+ |
|
469 |
+var _fulfill = function(promise, value) { |
|
470 |
+ if (promise._state !== PENDING) return; |
|
471 |
+ |
|
472 |
+ promise._state = FULFILLED; |
|
473 |
+ promise._value = value; |
|
474 |
+ promise._onRejected = []; |
|
475 |
+ |
|
476 |
+ var onFulfilled = promise._onFulfilled, fn; |
|
477 |
+ while (fn = onFulfilled.shift()) fn(value); |
|
478 |
+}; |
|
479 |
+ |
|
480 |
+var reject = Promise.reject = function(promise, reason) { |
|
481 |
+ if (promise._state !== PENDING) return; |
|
482 |
+ |
|
483 |
+ promise._state = REJECTED; |
|
484 |
+ promise._reason = reason; |
|
485 |
+ promise._onFulfilled = []; |
|
486 |
+ |
|
487 |
+ var onRejected = promise._onRejected, fn; |
|
488 |
+ while (fn = onRejected.shift()) fn(reason); |
|
489 |
+}; |
|
490 |
+ |
|
491 |
+Promise.all = function(promises) { |
|
492 |
+ return new Promise(function(fulfill, reject) { |
|
493 |
+ var list = [], |
|
494 |
+ n = promises.length, |
|
495 |
+ i; |
|
496 |
+ |
|
497 |
+ if (n === 0) return fulfill(list); |
|
498 |
+ |
|
499 |
+ for (i = 0; i < n; i++) (function(promise, i) { |
|
500 |
+ Promise.fulfilled(promise).then(function(value) { |
|
501 |
+ list[i] = value; |
|
502 |
+ if (--n === 0) fulfill(list); |
|
503 |
+ }, reject); |
|
504 |
+ })(promises[i], i); |
|
505 |
+ }); |
|
506 |
+}; |
|
507 |
+ |
|
508 |
+Promise.defer = defer; |
|
509 |
+ |
|
510 |
+Promise.deferred = Promise.pending = function() { |
|
511 |
+ var tuple = {}; |
|
512 |
+ |
|
513 |
+ tuple.promise = new Promise(function(fulfill, reject) { |
|
514 |
+ tuple.fulfill = tuple.resolve = fulfill; |
|
515 |
+ tuple.reject = reject; |
|
516 |
+ }); |
|
517 |
+ return tuple; |
|
518 |
+}; |
|
519 |
+ |
|
520 |
+Promise.fulfilled = Promise.resolved = function(value) { |
|
521 |
+ return new Promise(function(fulfill, reject) { fulfill(value) }); |
|
522 |
+}; |
|
523 |
+ |
|
524 |
+Promise.rejected = function(reason) { |
|
525 |
+ return new Promise(function(fulfill, reject) { reject(reason) }); |
|
526 |
+}; |
|
527 |
+ |
|
528 |
+if (typeof Faye === 'undefined') |
|
529 |
+ module.exports = Promise; |
|
530 |
+else |
|
531 |
+ Faye.Promise = Promise; |
|
532 |
+ |
|
533 |
+})(); |
|
534 |
+ |
|
535 |
+Faye.Set = Faye.Class({ |
|
536 |
+ initialize: function() { |
|
537 |
+ this._index = {}; |
|
538 |
+ }, |
|
539 |
+ |
|
540 |
+ add: function(item) { |
|
541 |
+ var key = (item.id !== undefined) ? item.id : item; |
|
542 |
+ if (this._index.hasOwnProperty(key)) return false; |
|
543 |
+ this._index[key] = item; |
|
544 |
+ return true; |
|
545 |
+ }, |
|
546 |
+ |
|
547 |
+ forEach: function(block, context) { |
|
548 |
+ for (var key in this._index) { |
|
549 |
+ if (this._index.hasOwnProperty(key)) |
|
550 |
+ block.call(context, this._index[key]); |
|
551 |
+ } |
|
552 |
+ }, |
|
553 |
+ |
|
554 |
+ isEmpty: function() { |
|
555 |
+ for (var key in this._index) { |
|
556 |
+ if (this._index.hasOwnProperty(key)) return false; |
|
557 |
+ } |
|
558 |
+ return true; |
|
559 |
+ }, |
|
560 |
+ |
|
561 |
+ member: function(item) { |
|
562 |
+ for (var key in this._index) { |
|
563 |
+ if (this._index[key] === item) return true; |
|
564 |
+ } |
|
565 |
+ return false; |
|
566 |
+ }, |
|
567 |
+ |
|
568 |
+ remove: function(item) { |
|
569 |
+ var key = (item.id !== undefined) ? item.id : item; |
|
570 |
+ var removed = this._index[key]; |
|
571 |
+ delete this._index[key]; |
|
572 |
+ return removed; |
|
573 |
+ }, |
|
574 |
+ |
|
575 |
+ toArray: function() { |
|
576 |
+ var array = []; |
|
577 |
+ this.forEach(function(item) { array.push(item) }); |
|
578 |
+ return array; |
|
579 |
+ } |
|
580 |
+}); |
|
581 |
+ |
|
582 |
+Faye.URI = { |
|
583 |
+ isURI: function(uri) { |
|
584 |
+ return uri && uri.protocol && uri.host && uri.path; |
|
585 |
+ }, |
|
586 |
+ |
|
587 |
+ isSameOrigin: function(uri) { |
|
588 |
+ var location = Faye.ENV.location; |
|
589 |
+ return uri.protocol === location.protocol && |
|
590 |
+ uri.hostname === location.hostname && |
|
591 |
+ uri.port === location.port; |
|
592 |
+ }, |
|
593 |
+ |
|
594 |
+ parse: function(url) { |
|
595 |
+ if (typeof url !== 'string') return url; |
|
596 |
+ var uri = {}, parts, query, pairs, i, n, data; |
|
597 |
+ |
|
598 |
+ var consume = function(name, pattern) { |
|
599 |
+ url = url.replace(pattern, function(match) { |
|
600 |
+ uri[name] = match; |
|
601 |
+ return ''; |
|
602 |
+ }); |
|
603 |
+ uri[name] = uri[name] || ''; |
|
604 |
+ }; |
|
605 |
+ |
|
606 |
+ consume('protocol', /^[a-z]+\:/i); |
|
607 |
+ consume('host', /^\/\/[^\/\?#]+/); |
|
608 |
+ |
|
609 |
+ if (!/^\//.test(url) && !uri.host) |
|
610 |
+ url = Faye.ENV.location.pathname.replace(/[^\/]*$/, '') + url; |
|
611 |
+ |
|
612 |
+ consume('pathname', /^[^\?#]*/); |
|
613 |
+ consume('search', /^\?[^#]*/); |
|
614 |
+ consume('hash', /^#.*/); |
|
615 |
+ |
|
616 |
+ uri.protocol = uri.protocol || Faye.ENV.location.protocol; |
|
617 |
+ |
|
618 |
+ if (uri.host) { |
|
619 |
+ uri.host = uri.host.substr(2); |
|
620 |
+ parts = uri.host.split(':'); |
|
621 |
+ uri.hostname = parts[0]; |
|
622 |
+ uri.port = parts[1] || ''; |
|
623 |
+ } else { |
|
624 |
+ uri.host = Faye.ENV.location.host; |
|
625 |
+ uri.hostname = Faye.ENV.location.hostname; |
|
626 |
+ uri.port = Faye.ENV.location.port; |
|
627 |
+ } |
|
628 |
+ |
|
629 |
+ uri.pathname = uri.pathname || '/'; |
|
630 |
+ uri.path = uri.pathname + uri.search; |
|
631 |
+ |
|
632 |
+ query = uri.search.replace(/^\?/, ''); |
|
633 |
+ pairs = query ? query.split('&') : []; |
|
634 |
+ data = {}; |
|
635 |
+ |
|
636 |
+ for (i = 0, n = pairs.length; i < n; i++) { |
|
637 |
+ parts = pairs[i].split('='); |
|
638 |
+ data[decodeURIComponent(parts[0] || '')] = decodeURIComponent(parts[1] || ''); |
|
639 |
+ } |
|
640 |
+ |
|
641 |
+ uri.query = data; |
|
642 |
+ |
|
643 |
+ uri.href = this.stringify(uri); |
|
644 |
+ return uri; |
|
645 |
+ }, |
|
646 |
+ |
|
647 |
+ stringify: function(uri) { |
|
648 |
+ var string = uri.protocol + '//' + uri.hostname; |
|
649 |
+ if (uri.port) string += ':' + uri.port; |
|
650 |
+ string += uri.pathname + this.queryString(uri.query) + (uri.hash || ''); |
|
651 |
+ return string; |
|
652 |
+ }, |
|
653 |
+ |
|
654 |
+ queryString: function(query) { |
|
655 |
+ var pairs = []; |
|
656 |
+ for (var key in query) { |
|
657 |
+ if (!query.hasOwnProperty(key)) continue; |
|
658 |
+ pairs.push(encodeURIComponent(key) + '=' + encodeURIComponent(query[key])); |
|
659 |
+ } |
|
660 |
+ if (pairs.length === 0) return ''; |
|
661 |
+ return '?' + pairs.join('&'); |
|
662 |
+ } |
|
663 |
+}; |
|
664 |
+ |
|
665 |
+Faye.Error = Faye.Class({ |
|
666 |
+ initialize: function(code, params, message) { |
|
667 |
+ this.code = code; |
|
668 |
+ this.params = Array.prototype.slice.call(params); |
|
669 |
+ this.message = message; |
|
670 |
+ }, |
|
671 |
+ |
|
672 |
+ toString: function() { |
|
673 |
+ return this.code + ':' + |
|
674 |
+ this.params.join(',') + ':' + |
|
675 |
+ this.message; |
|
676 |
+ } |
|
677 |
+}); |
|
678 |
+ |
|
679 |
+Faye.Error.parse = function(message) { |
|
680 |
+ message = message || ''; |
|
681 |
+ if (!Faye.Grammar.ERROR.test(message)) return new this(null, [], message); |
|
682 |
+ |
|
683 |
+ var parts = message.split(':'), |
|
684 |
+ code = parseInt(parts[0]), |
|
685 |
+ params = parts[1].split(','), |
|
686 |
+ message = parts[2]; |
|
687 |
+ |
|
688 |
+ return new this(code, params, message); |
|
689 |
+}; |
|
690 |
+ |
|
691 |
+ |
|
692 |
+ |
|
693 |
+ |
|
694 |
+Faye.Error.versionMismatch = function() { |
|
695 |
+ return new this(300, arguments, 'Version mismatch').toString(); |
|
696 |
+}; |
|
697 |
+ |
|
698 |
+Faye.Error.conntypeMismatch = function() { |
|
699 |
+ return new this(301, arguments, 'Connection types not supported').toString(); |
|
700 |
+}; |
|
701 |
+ |
|
702 |
+Faye.Error.extMismatch = function() { |
|
703 |
+ return new this(302, arguments, 'Extension mismatch').toString(); |
|
704 |
+}; |
|
705 |
+ |
|
706 |
+Faye.Error.badRequest = function() { |
|
707 |
+ return new this(400, arguments, 'Bad request').toString(); |
|
708 |
+}; |
|
709 |
+ |
|
710 |
+Faye.Error.clientUnknown = function() { |
|
711 |
+ return new this(401, arguments, 'Unknown client').toString(); |
|
712 |
+}; |
|
713 |
+ |
|
714 |
+Faye.Error.parameterMissing = function() { |
|
715 |
+ return new this(402, arguments, 'Missing required parameter').toString(); |
|
716 |
+}; |
|
717 |
+ |
|
718 |
+Faye.Error.channelForbidden = function() { |
|
719 |
+ return new this(403, arguments, 'Forbidden channel').toString(); |
|
720 |
+}; |
|
721 |
+ |
|
722 |
+Faye.Error.channelUnknown = function() { |
|
723 |
+ return new this(404, arguments, 'Unknown channel').toString(); |
|
724 |
+}; |
|
725 |
+ |
|
726 |
+Faye.Error.channelInvalid = function() { |
|
727 |
+ return new this(405, arguments, 'Invalid channel').toString(); |
|
728 |
+}; |
|
729 |
+ |
|
730 |
+Faye.Error.extUnknown = function() { |
|
731 |
+ return new this(406, arguments, 'Unknown extension').toString(); |
|
732 |
+}; |
|
733 |
+ |
|
734 |
+Faye.Error.publishFailed = function() { |
|
735 |
+ return new this(407, arguments, 'Failed to publish').toString(); |
|
736 |
+}; |
|
737 |
+ |
|
738 |
+Faye.Error.serverError = function() { |
|
739 |
+ return new this(500, arguments, 'Internal server error').toString(); |
|
740 |
+}; |
|
741 |
+ |
|
742 |
+ |
|
743 |
+Faye.Deferrable = { |
|
744 |
+ then: function(callback, errback) { |
|
745 |
+ var self = this; |
|
746 |
+ if (!this._promise) |
|
747 |
+ this._promise = new Faye.Promise(function(fulfill, reject) { |
|
748 |
+ self._fulfill = fulfill; |
|
749 |
+ self._reject = reject; |
|
750 |
+ }); |
|
751 |
+ |
|
752 |
+ if (arguments.length === 0) |
|
753 |
+ return this._promise; |
|
754 |
+ else |
|
755 |
+ return this._promise.then(callback, errback); |
|
756 |
+ }, |
|
757 |
+ |
|
758 |
+ callback: function(callback, context) { |
|
759 |
+ return this.then(function(value) { callback.call(context, value) }); |
|
760 |
+ }, |
|
761 |
+ |
|
762 |
+ errback: function(callback, context) { |
|
763 |
+ return this.then(null, function(reason) { callback.call(context, reason) }); |
|
764 |
+ }, |
|
765 |
+ |
|
766 |
+ timeout: function(seconds, message) { |
|
767 |
+ this.then(); |
|
768 |
+ var self = this; |
|
769 |
+ this._timer = Faye.ENV.setTimeout(function() { |
|
770 |
+ self._reject(message); |
|
771 |
+ }, seconds * 1000); |
|
772 |
+ }, |
|
773 |
+ |
|
774 |
+ setDeferredStatus: function(status, value) { |
|
775 |
+ if (this._timer) Faye.ENV.clearTimeout(this._timer); |
|
776 |
+ |
|
777 |
+ this.then(); |
|
778 |
+ |
|
779 |
+ if (status === 'succeeded') |
|
780 |
+ this._fulfill(value); |
|
781 |
+ else if (status === 'failed') |
|
782 |
+ this._reject(value); |
|
783 |
+ else |
|
784 |
+ delete this._promise; |
|
785 |
+ } |
|
786 |
+}; |
|
787 |
+ |
|
788 |
+Faye.Publisher = { |
|
789 |
+ countListeners: function(eventType) { |
|
790 |
+ return this.listeners(eventType).length; |
|
791 |
+ }, |
|
792 |
+ |
|
793 |
+ bind: function(eventType, listener, context) { |
|
794 |
+ var slice = Array.prototype.slice, |
|
795 |
+ handler = function() { listener.apply(context, slice.call(arguments)) }; |
|
796 |
+ |
|
797 |
+ this._listeners = this._listeners || []; |
|
798 |
+ this._listeners.push([eventType, listener, context, handler]); |
|
799 |
+ return this.on(eventType, handler); |
|
800 |
+ }, |
|
801 |
+ |
|
802 |
+ unbind: function(eventType, listener, context) { |
|
803 |
+ this._listeners = this._listeners || []; |
|
804 |
+ var n = this._listeners.length, tuple; |
|
805 |
+ |
|
806 |
+ while (n--) { |
|
807 |
+ tuple = this._listeners[n]; |
|
808 |
+ if (tuple[0] !== eventType) continue; |
|
809 |
+ if (listener && (tuple[1] !== listener || tuple[2] !== context)) continue; |
|
810 |
+ this._listeners.splice(n, 1); |
|
811 |
+ this.removeListener(eventType, tuple[3]); |
|
812 |
+ } |
|
813 |
+ } |
|
814 |
+}; |
|
815 |
+ |
|
816 |
+Faye.extend(Faye.Publisher, Faye.EventEmitter.prototype); |
|
817 |
+Faye.Publisher.trigger = Faye.Publisher.emit; |
|
818 |
+ |
|
819 |
+Faye.Timeouts = { |
|
820 |
+ addTimeout: function(name, delay, callback, context) { |
|
821 |
+ this._timeouts = this._timeouts || {}; |
|
822 |
+ if (this._timeouts.hasOwnProperty(name)) return; |
|
823 |
+ var self = this; |
|
824 |
+ this._timeouts[name] = Faye.ENV.setTimeout(function() { |
|
825 |
+ delete self._timeouts[name]; |
|
826 |
+ callback.call(context); |
|
827 |
+ }, 1000 * delay); |
|
828 |
+ }, |
|
829 |
+ |
|
830 |
+ removeTimeout: function(name) { |
|
831 |
+ this._timeouts = this._timeouts || {}; |
|
832 |
+ var timeout = this._timeouts[name]; |
|
833 |
+ if (!timeout) return; |
|
834 |
+ Faye.ENV.clearTimeout(timeout); |
|
835 |
+ delete this._timeouts[name]; |
|
836 |
+ }, |
|
837 |
+ |
|
838 |
+ removeAllTimeouts: function() { |
|
839 |
+ this._timeouts = this._timeouts || {}; |
|
840 |
+ for (var name in this._timeouts) this.removeTimeout(name); |
|
841 |
+ } |
|
842 |
+}; |
|
843 |
+ |
|
844 |
+Faye.Logging = { |
|
845 |
+ LOG_LEVELS: { |
|
846 |
+ fatal: 4, |
|
847 |
+ error: 3, |
|
848 |
+ warn: 2, |
|
849 |
+ info: 1, |
|
850 |
+ debug: 0 |
|
851 |
+ }, |
|
852 |
+ |
|
853 |
+ writeLog: function(messageArgs, level) { |
|
854 |
+ if (!Faye.logger) return; |
|
855 |
+ |
|
856 |
+ var args = Array.prototype.slice.apply(messageArgs), |
|
857 |
+ banner = '[Faye', |
|
858 |
+ klass = this.className, |
|
859 |
+ |
|
860 |
+ message = args.shift().replace(/\?/g, function() { |
|
861 |
+ try { |
|
862 |
+ return Faye.toJSON(args.shift()); |
|
863 |
+ } catch (e) { |
|
864 |
+ return '[Object]'; |
|
865 |
+ } |
|
866 |
+ }); |
|
867 |
+ |
|
868 |
+ for (var key in Faye) { |
|
869 |
+ if (klass) continue; |
|
870 |
+ if (typeof Faye[key] !== 'function') continue; |
|
871 |
+ if (this instanceof Faye[key]) klass = key; |
|
872 |
+ } |
|
873 |
+ if (klass) banner += '.' + klass; |
|
874 |
+ banner += '] '; |
|
875 |
+ |
|
876 |
+ if (typeof Faye.logger[level] === 'function') |
|
877 |
+ Faye.logger[level](banner + message); |
|
878 |
+ else if (typeof Faye.logger === 'function') |
|
879 |
+ Faye.logger(banner + message); |
|
880 |
+ } |
|
881 |
+}; |
|
882 |
+ |
|
883 |
+(function() { |
|
884 |
+ for (var key in Faye.Logging.LOG_LEVELS) |
|
885 |
+ (function(level) { |
|
886 |
+ Faye.Logging[level] = function() { |
|
887 |
+ this.writeLog(arguments, level); |
|
888 |
+ }; |
|
889 |
+ })(key); |
|
890 |
+})(); |
|
891 |
+ |
|
892 |
+Faye.Grammar = { |
|
893 |
+ CHANNEL_NAME: /^\/(((([a-z]|[A-Z])|[0-9])|(\-|\_|\!|\~|\(|\)|\$|\@)))+(\/(((([a-z]|[A-Z])|[0-9])|(\-|\_|\!|\~|\(|\)|\$|\@)))+)*$/, |
|
894 |
+ CHANNEL_PATTERN: /^(\/(((([a-z]|[A-Z])|[0-9])|(\-|\_|\!|\~|\(|\)|\$|\@)))+)*\/\*{1,2}$/, |
|
895 |
+ ERROR: /^([0-9][0-9][0-9]:(((([a-z]|[A-Z])|[0-9])|(\-|\_|\!|\~|\(|\)|\$|\@)| |\/|\*|\.))*(,(((([a-z]|[A-Z])|[0-9])|(\-|\_|\!|\~|\(|\)|\$|\@)| |\/|\*|\.))*)*:(((([a-z]|[A-Z])|[0-9])|(\-|\_|\!|\~|\(|\)|\$|\@)| |\/|\*|\.))*|[0-9][0-9][0-9]::(((([a-z]|[A-Z])|[0-9])|(\-|\_|\!|\~|\(|\)|\$|\@)| |\/|\*|\.))*)$/, |
|
896 |
+ VERSION: /^([0-9])+(\.(([a-z]|[A-Z])|[0-9])(((([a-z]|[A-Z])|[0-9])|\-|\_))*)*$/ |
|
897 |
+}; |
|
898 |
+ |
|
899 |
+Faye.Extensible = { |
|
900 |
+ addExtension: function(extension) { |
|
901 |
+ this._extensions = this._extensions || []; |
|
902 |
+ this._extensions.push(extension); |
|
903 |
+ if (extension.added) extension.added(this); |
|
904 |
+ }, |
|
905 |
+ |
|
906 |
+ removeExtension: function(extension) { |
|
907 |
+ if (!this._extensions) return; |
|
908 |
+ var i = this._extensions.length; |
|
909 |
+ while (i--) { |
|
910 |
+ if (this._extensions[i] !== extension) continue; |
|
911 |
+ this._extensions.splice(i,1); |
|
912 |
+ if (extension.removed) extension.removed(this); |
|
913 |
+ } |
|
914 |
+ }, |
|
915 |
+ |
|
916 |
+ pipeThroughExtensions: function(stage, message, request, callback, context) { |
|
917 |
+ this.debug('Passing through ? extensions: ?', stage, message); |
|
918 |
+ |
|
919 |
+ if (!this._extensions) return callback.call(context, message); |
|
920 |
+ var extensions = this._extensions.slice(); |
|
921 |
+ |
|
922 |
+ var pipe = function(message) { |
|
923 |
+ if (!message) return callback.call(context, message); |
|
924 |
+ |
|
925 |
+ var extension = extensions.shift(); |
|
926 |
+ if (!extension) return callback.call(context, message); |
|
927 |
+ |
|
928 |
+ var fn = extension[stage]; |
|
929 |
+ if (!fn) return pipe(message); |
|
930 |
+ |
|
931 |
+ if (fn.length >= 3) extension[stage](message, request, pipe); |
|
932 |
+ else extension[stage](message, pipe); |
|
933 |
+ }; |
|
934 |
+ pipe(message); |
|
935 |
+ } |
|
936 |
+}; |
|
937 |
+ |
|
938 |
+Faye.extend(Faye.Extensible, Faye.Logging); |
|
939 |
+ |
|
940 |
+Faye.Channel = Faye.Class({ |
|
941 |
+ initialize: function(name) { |
|
942 |
+ this.id = this.name = name; |
|
943 |
+ }, |
|
944 |
+ |
|
945 |
+ push: function(message) { |
|
946 |
+ this.trigger('message', message); |
|
947 |
+ }, |
|
948 |
+ |
|
949 |
+ isUnused: function() { |
|
950 |
+ return this.countListeners('message') === 0; |
|
951 |
+ } |
|
952 |
+}); |
|
953 |
+ |
|
954 |
+Faye.extend(Faye.Channel.prototype, Faye.Publisher); |
|
955 |
+ |
|
956 |
+Faye.extend(Faye.Channel, { |
|
957 |
+ HANDSHAKE: '/meta/handshake', |
|
958 |
+ CONNECT: '/meta/connect', |
|
959 |
+ SUBSCRIBE: '/meta/subscribe', |
|
960 |
+ UNSUBSCRIBE: '/meta/unsubscribe', |
|
961 |
+ DISCONNECT: '/meta/disconnect', |
|
962 |
+ |
|
963 |
+ META: 'meta', |
|
964 |
+ SERVICE: 'service', |
|
965 |
+ |
|
966 |
+ expand: function(name) { |
|
967 |
+ var segments = this.parse(name), |
|
968 |
+ channels = ['/**', name]; |
|
969 |
+ |
|
970 |
+ var copy = segments.slice(); |
|
971 |
+ copy[copy.length - 1] = '*'; |
|
972 |
+ channels.push(this.unparse(copy)); |
|
973 |
+ |
|
974 |
+ for (var i = 1, n = segments.length; i < n; i++) { |
|
975 |
+ copy = segments.slice(0, i); |
|
976 |
+ copy.push('**'); |
|
977 |
+ channels.push(this.unparse(copy)); |
|
978 |
+ } |
|
979 |
+ |
|
980 |
+ return channels; |
|
981 |
+ }, |
|
982 |
+ |
|
983 |
+ isValid: function(name) { |
|
984 |
+ return Faye.Grammar.CHANNEL_NAME.test(name) || |
|
985 |
+ Faye.Grammar.CHANNEL_PATTERN.test(name); |
|
986 |
+ }, |
|
987 |
+ |
|
988 |
+ parse: function(name) { |
|
989 |
+ if (!this.isValid(name)) return null; |
|
990 |
+ return name.split('/').slice(1); |
|
991 |
+ }, |
|
992 |
+ |
|
993 |
+ unparse: function(segments) { |
|
994 |
+ return '/' + segments.join('/'); |
|
995 |
+ }, |
|
996 |
+ |
|
997 |
+ isMeta: function(name) { |
|
998 |
+ var segments = this.parse(name); |
|
999 |
+ return segments ? (segments[0] === this.META) : null; |
|
1000 |
+ }, |
|
1001 |
+ |
|
1002 |
+ isService: function(name) { |
|
1003 |
+ var segments = this.parse(name); |
|
1004 |
+ return segments ? (segments[0] === this.SERVICE) : null; |
|
1005 |
+ }, |
|
1006 |
+ |
|
1007 |
+ isSubscribable: function(name) { |
|
1008 |
+ if (!this.isValid(name)) return null; |
|
1009 |
+ return !this.isMeta(name) && !this.isService(name); |
|
1010 |
+ }, |
|
1011 |
+ |
|
1012 |
+ Set: Faye.Class({ |
|
1013 |
+ initialize: function() { |
|
1014 |
+ this._channels = {}; |
|
1015 |
+ }, |
|
1016 |
+ |
|
1017 |
+ getKeys: function() { |
|
1018 |
+ var keys = []; |
|
1019 |
+ for (var key in this._channels) keys.push(key); |
|
1020 |
+ return keys; |
|
1021 |
+ }, |
|
1022 |
+ |
|
1023 |
+ remove: function(name) { |
|
1024 |
+ delete this._channels[name]; |
|
1025 |
+ }, |
|
1026 |
+ |
|
1027 |
+ hasSubscription: function(name) { |
|
1028 |
+ return this._channels.hasOwnProperty(name); |
|
1029 |
+ }, |
|
1030 |
+ |
|
1031 |
+ subscribe: function(names, callback, context) { |
|
1032 |
+ var name; |
|
1033 |
+ for (var i = 0, n = names.length; i < n; i++) { |
|
1034 |
+ name = names[i]; |
|
1035 |
+ var channel = this._channels[name] = this._channels[name] || new Faye.Channel(name); |
|
1036 |
+ if (callback) channel.bind('message', callback, context); |
|
1037 |
+ } |
|
1038 |
+ }, |
|
1039 |
+ |
|
1040 |
+ unsubscribe: function(name, callback, context) { |
|
1041 |
+ var channel = this._channels[name]; |
|
1042 |
+ if (!channel) return false; |
|
1043 |
+ channel.unbind('message', callback, context); |
|
1044 |
+ |
|
1045 |
+ if (channel.isUnused()) { |
|
1046 |
+ this.remove(name); |
|
1047 |
+ return true; |
|
1048 |
+ } else { |
|
1049 |
+ return false; |
|
1050 |
+ } |
|
1051 |
+ }, |
|
1052 |
+ |
|
1053 |
+ distributeMessage: function(message) { |
|
1054 |
+ var channels = Faye.Channel.expand(message.channel); |
|
1055 |
+ |
|
1056 |
+ for (var i = 0, n = channels.length; i < n; i++) { |
|
1057 |
+ var channel = this._channels[channels[i]]; |
|
1058 |
+ if (channel) channel.trigger('message', message.data); |
|
1059 |
+ } |
|
1060 |
+ } |
|
1061 |
+ }) |
|
1062 |
+}); |
|
1063 |
+ |
|
1064 |
+Faye.Publication = Faye.Class(Faye.Deferrable); |
|
1065 |
+ |
|
1066 |
+Faye.Subscription = Faye.Class({ |
|
1067 |
+ initialize: function(client, channels, callback, context) { |
|
1068 |
+ this._client = client; |
|
1069 |
+ this._channels = channels; |
|
1070 |
+ this._callback = callback; |
|
1071 |
+ this._context = context; |
|
1072 |
+ this._cancelled = false; |
|
1073 |
+ }, |
|
1074 |
+ |
|
1075 |
+ cancel: function() { |
|
1076 |
+ if (this._cancelled) return; |
|
1077 |
+ this._client.unsubscribe(this._channels, this._callback, this._context); |
|
1078 |
+ this._cancelled = true; |
|
1079 |
+ }, |
|
1080 |
+ |
|
1081 |
+ unsubscribe: function() { |
|
1082 |
+ this.cancel(); |
|
1083 |
+ } |
|
1084 |
+}); |
|
1085 |
+ |
|
1086 |
+Faye.extend(Faye.Subscription.prototype, Faye.Deferrable); |
|
1087 |
+ |
|
1088 |
+Faye.Client = Faye.Class({ |
|
1089 |
+ UNCONNECTED: 1, |
|
1090 |
+ CONNECTING: 2, |
|
1091 |
+ CONNECTED: 3, |
|
1092 |
+ DISCONNECTED: 4, |
|
1093 |
+ |
|
1094 |
+ HANDSHAKE: 'handshake', |
|
1095 |
+ RETRY: 'retry', |
|
1096 |
+ NONE: 'none', |
|
1097 |
+ |
|
1098 |
+ CONNECTION_TIMEOUT: 60, |
|
1099 |
+ |
|
1100 |
+ DEFAULT_ENDPOINT: '/bayeux', |
|
1101 |
+ INTERVAL: 0, |
|
1102 |
+ |
|
1103 |
+ initialize: function(endpoint, options) { |
|
1104 |
+ this.info('New client created for ?', endpoint); |
|
1105 |
+ options = options || {}; |
|
1106 |
+ |
|
1107 |
+ Faye.validateOptions(options, ['interval', 'timeout', 'endpoints', 'proxy', 'retry', 'scheduler', 'websocketExtensions', 'tls', 'ca']); |
|
1108 |
+ |
|
1109 |
+ this._endpoint = endpoint || this.DEFAULT_ENDPOINT; |
|
1110 |
+ this._channels = new Faye.Channel.Set(); |
|
1111 |
+ this._dispatcher = new Faye.Dispatcher(this, this._endpoint, options); |
|
1112 |
+ |
|
1113 |
+ this._messageId = 0; |
|
1114 |
+ this._state = this.UNCONNECTED; |
|
1115 |
+ |
|
1116 |
+ this._responseCallbacks = {}; |
|
1117 |
+ |
|
1118 |
+ this._advice = { |
|
1119 |
+ reconnect: this.RETRY, |
|
1120 |
+ interval: 1000 * (options.interval || this.INTERVAL), |
|
1121 |
+ timeout: 1000 * (options.timeout || this.CONNECTION_TIMEOUT) |
|
1122 |
+ }; |
|
1123 |
+ this._dispatcher.timeout = this._advice.timeout / 1000; |
|
1124 |
+ |
|
1125 |
+ this._dispatcher.bind('message', this._receiveMessage, this); |
|
1126 |
+ |
|
1127 |
+ if (Faye.Event && Faye.ENV.onbeforeunload !== undefined) |
|
1128 |
+ Faye.Event.on(Faye.ENV, 'beforeunload', function() { |
|
1129 |
+ if (Faye.indexOf(this._dispatcher._disabled, 'autodisconnect') < 0) |
|
1130 |
+ this.disconnect(); |
|
1131 |
+ }, this); |
|
1132 |
+ }, |
|
1133 |
+ |
|
1134 |
+ addWebsocketExtension: function(extension) { |
|
1135 |
+ return this._dispatcher.addWebsocketExtension(extension); |
|
1136 |
+ }, |
|
1137 |
+ |
|
1138 |
+ disable: function(feature) { |
|
1139 |
+ return this._dispatcher.disable(feature); |
|
1140 |
+ }, |
|
1141 |
+ |
|
1142 |
+ setHeader: function(name, value) { |
|
1143 |
+ return this._dispatcher.setHeader(name, value); |
|
1144 |
+ }, |
|
1145 |
+ |
|
1146 |
+ // Request |
|
1147 |
+ // MUST include: * channel |
|
1148 |
+ // * version |
|
1149 |
+ // * supportedConnectionTypes |
|
1150 |
+ // MAY include: * minimumVersion |
|
1151 |
+ // * ext |
|
1152 |
+ // * id |
|
1153 |
+ // |
|
1154 |
+ // Success Response Failed Response |
|
1155 |
+ // MUST include: * channel MUST include: * channel |
|
1156 |
+ // * version * successful |
|
1157 |
+ // * supportedConnectionTypes * error |
|
1158 |
+ // * clientId MAY include: * supportedConnectionTypes |
|
1159 |
+ // * successful * advice |
|
1160 |
+ // MAY include: * minimumVersion * version |
|
1161 |
+ // * advice * minimumVersion |
|
1162 |
+ // * ext * ext |
|
1163 |
+ // * id * id |
|
1164 |
+ // * authSuccessful |
|
1165 |
+ handshake: function(callback, context) { |
|
1166 |
+ if (this._advice.reconnect === this.NONE) return; |
|
1167 |
+ if (this._state !== this.UNCONNECTED) return; |
|
1168 |
+ |
|
1169 |
+ this._state = this.CONNECTING; |
|
1170 |
+ var self = this; |
|
1171 |
+ |
|
1172 |
+ this.info('Initiating handshake with ?', Faye.URI.stringify(this._endpoint)); |
|
1173 |
+ this._dispatcher.selectTransport(Faye.MANDATORY_CONNECTION_TYPES); |
|
1174 |
+ |
|
1175 |
+ this._sendMessage({ |
|
1176 |
+ channel: Faye.Channel.HANDSHAKE, |
|
1177 |
+ version: Faye.BAYEUX_VERSION, |
|
1178 |
+ supportedConnectionTypes: this._dispatcher.getConnectionTypes() |
|
1179 |
+ |
|
1180 |
+ }, {}, function(response) { |
|
1181 |
+ |
|
1182 |
+ if (response.successful) { |
|
1183 |
+ this._state = this.CONNECTED; |
|
1184 |
+ this._dispatcher.clientId = response.clientId; |
|
1185 |
+ |
|
1186 |
+ this._dispatcher.selectTransport(response.supportedConnectionTypes); |
|
1187 |
+ |
|
1188 |
+ this.info('Handshake successful: ?', this._dispatcher.clientId); |
|
1189 |
+ |
|
1190 |
+ this.subscribe(this._channels.getKeys(), true); |
|
1191 |
+ if (callback) Faye.Promise.defer(function() { callback.call(context) }); |
|
1192 |
+ |
|
1193 |
+ } else { |
|
1194 |
+ this.info('Handshake unsuccessful'); |
|
1195 |
+ Faye.ENV.setTimeout(function() { self.handshake(callback, context) }, this._dispatcher.retry * 1000); |
|
1196 |
+ this._state = this.UNCONNECTED; |
|
1197 |
+ } |
|
1198 |
+ }, this); |
|
1199 |
+ }, |
|
1200 |
+ |
|
1201 |
+ // Request Response |
|
1202 |
+ // MUST include: * channel MUST include: * channel |
|
1203 |
+ // * clientId * successful |
|
1204 |
+ // * connectionType * clientId |
|
1205 |
+ // MAY include: * ext MAY include: * error |
|
1206 |
+ // * id * advice |
|
1207 |
+ // * ext |
|
1208 |
+ // * id |
|
1209 |
+ // * timestamp |
|
1210 |
+ connect: function(callback, context) { |
|
1211 |
+ if (this._advice.reconnect === this.NONE) return; |
|
1212 |
+ if (this._state === this.DISCONNECTED) return; |
|
1213 |
+ |
|
1214 |
+ if (this._state === this.UNCONNECTED) |
|
1215 |
+ return this.handshake(function() { this.connect(callback, context) }, this); |
|
1216 |
+ |
|
1217 |
+ this.callback(callback, context); |
|
1218 |
+ if (this._state !== this.CONNECTED) return; |
|
1219 |
+ |
|
1220 |
+ this.info('Calling deferred actions for ?', this._dispatcher.clientId); |
|
1221 |
+ this.setDeferredStatus('succeeded'); |
|
1222 |
+ this.setDeferredStatus('unknown'); |
|
1223 |
+ |
|
1224 |
+ if (this._connectRequest) return; |
|
1225 |
+ this._connectRequest = true; |
|
1226 |
+ |
|
1227 |
+ this.info('Initiating connection for ?', this._dispatcher.clientId); |
|
1228 |
+ |
|
1229 |
+ this._sendMessage({ |
|
1230 |
+ channel: Faye.Channel.CONNECT, |
|
1231 |
+ clientId: this._dispatcher.clientId, |
|
1232 |
+ connectionType: this._dispatcher.connectionType |
|
1233 |
+ |
|
1234 |
+ }, {}, this._cycleConnection, this); |
|
1235 |
+ }, |
|
1236 |
+ |
|
1237 |
+ // Request Response |
|
1238 |
+ // MUST include: * channel MUST include: * channel |
|
1239 |
+ // * clientId * successful |
|
1240 |
+ // MAY include: * ext * clientId |
|
1241 |
+ // * id MAY include: * error |
|
1242 |
+ // * ext |
|
1243 |
+ // * id |
|
1244 |
+ disconnect: function() { |
|
1245 |
+ if (this._state !== this.CONNECTED) return; |
|
1246 |
+ this._state = this.DISCONNECTED; |
|
1247 |
+ |
|
1248 |
+ this.info('Disconnecting ?', this._dispatcher.clientId); |
|
1249 |
+ var promise = new Faye.Publication(); |
|
1250 |
+ |
|
1251 |
+ this._sendMessage({ |
|
1252 |
+ channel: Faye.Channel.DISCONNECT, |
|
1253 |
+ clientId: this._dispatcher.clientId |
|
1254 |
+ |
|
1255 |
+ }, {}, function(response) { |
|
1256 |
+ if (response.successful) { |
|
1257 |
+ this._dispatcher.close(); |
|
1258 |
+ promise.setDeferredStatus('succeeded'); |
|
1259 |
+ } else { |
|
1260 |
+ promise.setDeferredStatus('failed', Faye.Error.parse(response.error)); |
|
1261 |
+ } |
|
1262 |
+ }, this); |
|
1263 |
+ |
|
1264 |
+ this.info('Clearing channel listeners for ?', this._dispatcher.clientId); |
|
1265 |
+ this._channels = new Faye.Channel.Set(); |
|
1266 |
+ |
|
1267 |
+ return promise; |
|
1268 |
+ }, |
|
1269 |
+ |
|
1270 |
+ // Request Response |
|
1271 |
+ // MUST include: * channel MUST include: * channel |
|
1272 |
+ // * clientId * successful |
|
1273 |
+ // * subscription * clientId |
|
1274 |
+ // MAY include: * ext * subscription |
|
1275 |
+ // * id MAY include: * error |
|
1276 |
+ // * advice |
|
1277 |
+ // * ext |
|
1278 |
+ // * id |
|
1279 |
+ // * timestamp |
|
1280 |
+ subscribe: function(channel, callback, context) { |
|
1281 |
+ if (channel instanceof Array) |
|
1282 |
+ return Faye.map(channel, function(c) { |
|
1283 |
+ return this.subscribe(c, callback, context); |
|
1284 |
+ }, this); |
|
1285 |
+ |
|
1286 |
+ var subscription = new Faye.Subscription(this, channel, callback, context), |
|
1287 |
+ force = (callback === true), |
|
1288 |
+ hasSubscribe = this._channels.hasSubscription(channel); |
|
1289 |
+ |
|
1290 |
+ if (hasSubscribe && !force) { |
|
1291 |
+ this._channels.subscribe([channel], callback, context); |
|
1292 |
+ subscription.setDeferredStatus('succeeded'); |
|
1293 |
+ return subscription; |
|
1294 |
+ } |
|
1295 |
+ |
|
1296 |
+ this.connect(function() { |
|
1297 |
+ this.info('Client ? attempting to subscribe to ?', this._dispatcher.clientId, channel); |
|
1298 |
+ if (!force) this._channels.subscribe([channel], callback, context); |
|
1299 |
+ |
|
1300 |
+ this._sendMessage({ |
|
1301 |
+ channel: Faye.Channel.SUBSCRIBE, |
|
1302 |
+ clientId: this._dispatcher.clientId, |
|
1303 |
+ subscription: channel |
|
1304 |
+ |
|
1305 |
+ }, {}, function(response) { |
|
1306 |
+ if (!response.successful) { |
|
1307 |
+ subscription.setDeferredStatus('failed', Faye.Error.parse(response.error)); |
|
1308 |
+ return this._channels.unsubscribe(channel, callback, context); |
|
1309 |
+ } |
|
1310 |
+ |
|
1311 |
+ var channels = [].concat(response.subscription); |
|
1312 |
+ this.info('Subscription acknowledged for ? to ?', this._dispatcher.clientId, channels); |
|
1313 |
+ subscription.setDeferredStatus('succeeded'); |
|
1314 |
+ }, this); |
|
1315 |
+ }, this); |
|
1316 |
+ |
|
1317 |
+ return subscription; |
|
1318 |
+ }, |
|
1319 |
+ |
|
1320 |
+ // Request Response |
|
1321 |
+ // MUST include: * channel MUST include: * channel |
|
1322 |
+ // * clientId * successful |
|
1323 |
+ // * subscription * clientId |
|
1324 |
+ // MAY include: * ext * subscription |
|
1325 |
+ // * id MAY include: * error |
|
1326 |
+ // * advice |
|
1327 |
+ // * ext |
|
1328 |
+ // * id |
|
1329 |
+ // * timestamp |
|
1330 |
+ unsubscribe: function(channel, callback, context) { |
|
1331 |
+ if (channel instanceof Array) |
|
1332 |
+ return Faye.map(channel, function(c) { |
|
1333 |
+ return this.unsubscribe(c, callback, context); |
|
1334 |
+ }, this); |
|
1335 |
+ |
|
1336 |
+ var dead = this._channels.unsubscribe(channel, callback, context); |
|
1337 |
+ if (!dead) return; |
|
1338 |
+ |
|
1339 |
+ this.connect(function() { |
|
1340 |
+ this.info('Client ? attempting to unsubscribe from ?', this._dispatcher.clientId, channel); |
|
1341 |
+ |
|
1342 |
+ this._sendMessage({ |
|
1343 |
+ channel: Faye.Channel.UNSUBSCRIBE, |
|
1344 |
+ clientId: this._dispatcher.clientId, |
|
1345 |
+ subscription: channel |
|
1346 |
+ |
|
1347 |
+ }, {}, function(response) { |
|
1348 |
+ if (!response.successful) return; |
|
1349 |
+ |
|
1350 |
+ var channels = [].concat(response.subscription); |
|
1351 |
+ this.info('Unsubscription acknowledged for ? from ?', this._dispatcher.clientId, channels); |
|
1352 |
+ }, this); |
|
1353 |
+ }, this); |
|
1354 |
+ }, |
|
1355 |
+ |
|
1356 |
+ // Request Response |
|
1357 |
+ // MUST include: * channel MUST include: * channel |
|
1358 |
+ // * data * successful |
|
1359 |
+ // MAY include: * clientId MAY include: * id |
|
1360 |
+ // * id * error |
|
1361 |
+ // * ext * ext |
|
1362 |
+ publish: function(channel, data, options) { |
|
1363 |
+ Faye.validateOptions(options || {}, ['attempts', 'deadline']); |
|
1364 |
+ var publication = new Faye.Publication(); |
|
1365 |
+ |
|
1366 |
+ this.connect(function() { |
|
1367 |
+ this.info('Client ? queueing published message to ?: ?', this._dispatcher.clientId, channel, data); |
|
1368 |
+ |
|
1369 |
+ this._sendMessage({ |
|
1370 |
+ channel: channel, |
|
1371 |
+ data: data, |
|
1372 |
+ clientId: this._dispatcher.clientId |
|
1373 |
+ |
|
1374 |
+ }, options, function(response) { |
|
1375 |
+ if (response.successful) |
|
1376 |
+ publication.setDeferredStatus('succeeded'); |
|
1377 |
+ else |
|
1378 |
+ publication.setDeferredStatus('failed', Faye.Error.parse(response.error)); |
|
1379 |
+ }, this); |
|
1380 |
+ }, this); |
|
1381 |
+ |
|
1382 |
+ return publication; |
|
1383 |
+ }, |
|
1384 |
+ |
|
1385 |
+ _sendMessage: function(message, options, callback, context) { |
|
1386 |
+ message.id = this._generateMessageId(); |
|
1387 |
+ |
|
1388 |
+ var timeout = this._advice.timeout |
|
1389 |
+ ? 1.2 * this._advice.timeout / 1000 |
|
1390 |
+ : 1.2 * this._dispatcher.retry; |
|
1391 |
+ |
|
1392 |
+ this.pipeThroughExtensions('outgoing', message, null, function(message) { |
|
1393 |
+ if (!message) return; |
|
1394 |
+ if (callback) this._responseCallbacks[message.id] = [callback, context]; |
|
1395 |
+ this._dispatcher.sendMessage(message, timeout, options || {}); |
|
1396 |
+ }, this); |
|
1397 |
+ }, |
|
1398 |
+ |
|
1399 |
+ _generateMessageId: function() { |
|
1400 |
+ this._messageId += 1; |
|
1401 |
+ if (this._messageId >= Math.pow(2,32)) this._messageId = 0; |
|
1402 |
+ return this._messageId.toString(36); |
|
1403 |
+ }, |
|
1404 |
+ |
|
1405 |
+ _receiveMessage: function(message) { |
|
1406 |
+ var id = message.id, callback; |
|
1407 |
+ |
|
1408 |
+ if (message.successful !== undefined) { |
|
1409 |
+ callback = this._responseCallbacks[id]; |
|
1410 |
+ delete this._responseCallbacks[id]; |
|
1411 |
+ } |
|
1412 |
+ |
|
1413 |
+ this.pipeThroughExtensions('incoming', message, null, function(message) { |
|
1414 |
+ if (!message) return; |
|
1415 |
+ if (message.advice) this._handleAdvice(message.advice); |
|
1416 |
+ this._deliverMessage(message); |
|
1417 |
+ if (callback) callback[0].call(callback[1], message); |
|
1418 |
+ }, this); |
|
1419 |
+ }, |
|
1420 |
+ |
|
1421 |
+ _handleAdvice: function(advice) { |
|
1422 |
+ Faye.extend(this._advice, advice); |
|
1423 |
+ this._dispatcher.timeout = this._advice.timeout / 1000; |
|
1424 |
+ |
|
1425 |
+ if (this._advice.reconnect === this.HANDSHAKE && this._state !== this.DISCONNECTED) { |
|
1426 |
+ this._state = this.UNCONNECTED; |
|
1427 |
+ this._dispatcher.clientId = null; |
|
1428 |
+ this._cycleConnection(); |
|
1429 |
+ } |
|
1430 |
+ }, |
|
1431 |
+ |
|
1432 |
+ _deliverMessage: function(message) { |
|
1433 |
+ if (!message.channel || message.data === undefined) return; |
|
1434 |
+ this.info('Client ? calling listeners for ? with ?', this._dispatcher.clientId, message.channel, message.data); |
|
1435 |
+ this._channels.distributeMessage(message); |
|
1436 |
+ }, |
|
1437 |
+ |
|
1438 |
+ _cycleConnection: function() { |
|
1439 |
+ if (this._connectRequest) { |
|
1440 |
+ this._connectRequest = null; |
|
1441 |
+ this.info('Closed connection for ?', this._dispatcher.clientId); |
|
1442 |
+ } |
|
1443 |
+ var self = this; |
|
1444 |
+ Faye.ENV.setTimeout(function() { self.connect() }, this._advice.interval); |
|
1445 |
+ } |
|
1446 |
+}); |
|
1447 |
+ |
|
1448 |
+Faye.extend(Faye.Client.prototype, Faye.Deferrable); |
|
1449 |
+Faye.extend(Faye.Client.prototype, Faye.Publisher); |
|
1450 |
+Faye.extend(Faye.Client.prototype, Faye.Logging); |
|
1451 |
+Faye.extend(Faye.Client.prototype, Faye.Extensible); |
|
1452 |
+ |
|
1453 |
+Faye.Dispatcher = Faye.Class({ |
|
1454 |
+ MAX_REQUEST_SIZE: 2048, |
|
1455 |
+ DEFAULT_RETRY: 5, |
|
1456 |
+ |
|
1457 |
+ UP: 1, |
|
1458 |
+ DOWN: 2, |
|
1459 |
+ |
|
1460 |
+ initialize: function(client, endpoint, options) { |
|
1461 |
+ this._client = client; |
|
1462 |
+ this.endpoint = Faye.URI.parse(endpoint); |
|
1463 |
+ this._alternates = options.endpoints || {}; |
|
1464 |
+ |
|
1465 |
+ this.cookies = Faye.Cookies && new Faye.Cookies.CookieJar(); |
|
1466 |
+ this._disabled = []; |
|
1467 |
+ this._envelopes = {}; |
|
1468 |
+ this.headers = {}; |
|
1469 |
+ this.retry = options.retry || this.DEFAULT_RETRY; |
|
1470 |
+ this._scheduler = options.scheduler || Faye.Scheduler; |
|
1471 |
+ this._state = 0; |
|
1472 |
+ this.transports = {}; |
|
1473 |
+ this.wsExtensions = []; |
|
1474 |
+ |
|
1475 |
+ this.proxy = options.proxy || {}; |
|
1476 |
+ if (typeof this._proxy === 'string') this._proxy = {origin: this._proxy}; |
|
1477 |
+ |
|
1478 |
+ var exts = options.websocketExtensions; |
|
1479 |
+ if (exts) { |
|
1480 |
+ exts = [].concat(exts); |
|
1481 |
+ for (var i = 0, n = exts.length; i < n; i++) |
|
1482 |
+ this.addWebsocketExtension(exts[i]); |
|
1483 |
+ } |
|
1484 |
+ |
|
1485 |
+ this.tls = options.tls || {}; |
|
1486 |
+ this.tls.ca = this.tls.ca || options.ca; |
|
1487 |
+ |
|
1488 |
+ for (var type in this._alternates) |
|
1489 |
+ this._alternates[type] = Faye.URI.parse(this._alternates[type]); |
|
1490 |
+ |
|
1491 |
+ this.maxRequestSize = this.MAX_REQUEST_SIZE; |
|
1492 |
+ }, |
|
1493 |
+ |
|
1494 |
+ endpointFor: function(connectionType) { |
|
1495 |
+ return this._alternates[connectionType] || this.endpoint; |
|
1496 |
+ }, |
|
1497 |
+ |
|
1498 |
+ addWebsocketExtension: function(extension) { |
|
1499 |
+ this.wsExtensions.push(extension); |
|
1500 |
+ }, |
|
1501 |
+ |
|
1502 |
+ disable: function(feature) { |
|
1503 |
+ this._disabled.push(feature); |
|
1504 |
+ }, |
|
1505 |
+ |
|
1506 |
+ setHeader: function(name, value) { |
|
1507 |
+ this.headers[name] = value; |
|
1508 |
+ }, |
|
1509 |
+ |
|
1510 |
+ close: function() { |
|
1511 |
+ var transport = this._transport; |
|
1512 |
+ delete this._transport; |
|
1513 |
+ if (transport) transport.close(); |
|
1514 |
+ }, |
|
1515 |
+ |
|
1516 |
+ getConnectionTypes: function() { |
|
1517 |
+ return Faye.Transport.getConnectionTypes(); |
|
1518 |
+ }, |
|
1519 |
+ |
|
1520 |
+ selectTransport: function(transportTypes) { |
|
1521 |
+ Faye.Transport.get(this, transportTypes, this._disabled, function(transport) { |
|
1522 |
+ this.debug('Selected ? transport for ?', transport.connectionType, Faye.URI.stringify(transport.endpoint)); |
|
1523 |
+ |
|
1524 |
+ if (transport === this._transport) return; |
|
1525 |
+ if (this._transport) this._transport.close(); |
|
1526 |
+ |
|
1527 |
+ this._transport = transport; |
|
1528 |
+ this.connectionType = transport.connectionType; |
|
1529 |
+ }, this); |
|
1530 |
+ }, |
|
1531 |
+ |
|
1532 |
+ sendMessage: function(message, timeout, options) { |
|
1533 |
+ options = options || {}; |
|
1534 |
+ |
|
1535 |
+ var id = message.id, |
|
1536 |
+ attempts = options.attempts, |
|
1537 |
+ deadline = options.deadline && new Date().getTime() + (options.deadline * 1000), |
|
1538 |
+ envelope = this._envelopes[id], |
|
1539 |
+ scheduler; |
|
1540 |
+ |
|
1541 |
+ if (!envelope) { |
|
1542 |
+ scheduler = new this._scheduler(message, {timeout: timeout, interval: this.retry, attempts: attempts, deadline: deadline}); |
|
1543 |
+ envelope = this._envelopes[id] = {message: message, scheduler: scheduler}; |
|
1544 |
+ } |
|
1545 |
+ |
|
1546 |
+ this._sendEnvelope(envelope); |
|
1547 |
+ }, |
|
1548 |
+ |
|
1549 |
+ _sendEnvelope: function(envelope) { |
|
1550 |
+ if (!this._transport) return; |
|
1551 |
+ if (envelope.request || envelope.timer) return; |
|
1552 |
+ |
|
1553 |
+ var message = envelope.message, |
|
1554 |
+ scheduler = envelope.scheduler, |
|
1555 |
+ self = this; |
|
1556 |
+ |
|
1557 |
+ if (!scheduler.isDeliverable()) { |
|
1558 |
+ scheduler.abort(); |
|
1559 |
+ delete this._envelopes[message.id]; |
|
1560 |
+ return; |
|
1561 |
+ } |
|
1562 |
+ |
|
1563 |
+ envelope.timer = Faye.ENV.setTimeout(function() { |
|
1564 |
+ self.handleError(message); |
|
1565 |
+ }, scheduler.getTimeout() * 1000); |
|
1566 |
+ |
|
1567 |
+ scheduler.send(); |
|
1568 |
+ envelope.request = this._transport.sendMessage(message); |
|
1569 |
+ }, |
|
1570 |
+ |
|
1571 |
+ handleResponse: function(reply) { |
|
1572 |
+ var envelope = this._envelopes[reply.id]; |
|
1573 |
+ |
|
1574 |
+ if (reply.successful !== undefined && envelope) { |
|
1575 |
+ envelope.scheduler.succeed(); |
|
1576 |
+ delete this._envelopes[reply.id]; |
|
1577 |
+ Faye.ENV.clearTimeout(envelope.timer); |
|
1578 |
+ } |
|
1579 |
+ |
|
1580 |
+ this.trigger('message', reply); |
|
1581 |
+ |
|
1582 |
+ if (this._state === this.UP) return; |
|
1583 |
+ this._state = this.UP; |
|
1584 |
+ this._client.trigger('transport:up'); |
|
1585 |
+ }, |
|
1586 |
+ |
|
1587 |
+ handleError: function(message, immediate) { |
|
1588 |
+ var envelope = this._envelopes[message.id], |
|
1589 |
+ request = envelope && envelope.request, |
|
1590 |
+ self = this; |
|
1591 |
+ |
|
1592 |
+ if (!request) return; |
|
1593 |
+ |
|
1594 |
+ request.then(function(req) { |
|
1595 |
+ if (req && req.abort) req.abort(); |
|
1596 |
+ }); |
|
1597 |
+ |
|
1598 |
+ var scheduler = envelope.scheduler; |
|
1599 |
+ scheduler.fail(); |
|
1600 |
+ |
|
1601 |
+ Faye.ENV.clearTimeout(envelope.timer); |
|
1602 |
+ envelope.request = envelope.timer = null; |
|
1603 |
+ |
|
1604 |
+ if (immediate) { |
|
1605 |
+ this._sendEnvelope(envelope); |
|
1606 |
+ } else { |
|
1607 |
+ envelope.timer = Faye.ENV.setTimeout(function() { |
|
1608 |
+ envelope.timer = null; |
|
1609 |
+ self._sendEnvelope(envelope); |
|
1610 |
+ }, scheduler.getInterval() * 1000); |
|
1611 |
+ } |
|
1612 |
+ |
|
1613 |
+ if (this._state === this.DOWN) return; |
|
1614 |
+ this._state = this.DOWN; |
|
1615 |
+ this._client.trigger('transport:down'); |
|
1616 |
+ } |
|
1617 |
+}); |
|
1618 |
+ |
|
1619 |
+Faye.extend(Faye.Dispatcher.prototype, Faye.Publisher); |
|
1620 |
+Faye.extend(Faye.Dispatcher.prototype, Faye.Logging); |
|
1621 |
+ |
|
1622 |
+Faye.Scheduler = function(message, options) { |
|
1623 |
+ this.message = message; |
|
1624 |
+ this.options = options; |
|
1625 |
+ this.attempts = 0; |
|
1626 |
+}; |
|
1627 |
+ |
|
1628 |
+Faye.extend(Faye.Scheduler.prototype, { |
|
1629 |
+ getTimeout: function() { |
|
1630 |
+ return this.options.timeout; |
|
1631 |
+ }, |
|
1632 |
+ |
|
1633 |
+ getInterval: function() { |
|
1634 |
+ return this.options.interval; |
|
1635 |
+ }, |
|
1636 |
+ |
|
1637 |
+ isDeliverable: function() { |
|
1638 |
+ var attempts = this.options.attempts, |
|
1639 |
+ made = this.attempts, |
|
1640 |
+ deadline = this.options.deadline, |
|
1641 |
+ now = new Date().getTime(); |
|
1642 |
+ |
|
1643 |
+ if (attempts !== undefined && made >= attempts) |
|
1644 |
+ return false; |
|
1645 |
+ |
|
1646 |
+ if (deadline !== undefined && now > deadline) |
|
1647 |
+ return false; |
|
1648 |
+ |
|
1649 |
+ return true; |
|
1650 |
+ }, |
|
1651 |
+ |
|
1652 |
+ send: function() { |
|
1653 |
+ this.attempts += 1; |
|
1654 |
+ }, |
|
1655 |
+ |
|
1656 |
+ succeed: function() {}, |
|
1657 |
+ |
|
1658 |
+ fail: function() {}, |
|
1659 |
+ |
|
1660 |
+ abort: function() {} |
|
1661 |
+}); |
|
1662 |
+ |
|
1663 |
+Faye.Transport = Faye.extend(Faye.Class({ |
|
1664 |
+ DEFAULT_PORTS: {'http:': 80, 'https:': 443, 'ws:': 80, 'wss:': 443}, |
|
1665 |
+ SECURE_PROTOCOLS: ['https:', 'wss:'], |
|
1666 |
+ MAX_DELAY: 0, |
|
1667 |
+ |
|
1668 |
+ batching: true, |
|
1669 |
+ |
|
1670 |
+ initialize: function(dispatcher, endpoint) { |
|
1671 |
+ this._dispatcher = dispatcher; |
|
1672 |
+ this.endpoint = endpoint; |
|
1673 |
+ this._outbox = []; |
|
1674 |
+ this._proxy = Faye.extend({}, this._dispatcher.proxy); |
|
1675 |
+ |
|
1676 |
+ if (!this._proxy.origin && Faye.NodeAdapter) { |
|
1677 |
+ this._proxy.origin = Faye.indexOf(this.SECURE_PROTOCOLS, this.endpoint.protocol) >= 0 |
|
1678 |
+ ? (process.env.HTTPS_PROXY || process.env.https_proxy) |
|
1679 |
+ : (process.env.HTTP_PROXY || process.env.http_proxy); |
|
1680 |
+ } |
|
1681 |
+ }, |
|
1682 |
+ |
|
1683 |
+ close: function() {}, |
|
1684 |
+ |
|
1685 |
+ encode: function(messages) { |
|
1686 |
+ return ''; |
|
1687 |
+ }, |
|
1688 |
+ |
|
1689 |
+ sendMessage: function(message) { |
|
1690 |
+ this.debug('Client ? sending message to ?: ?', |
|
1691 |
+ this._dispatcher.clientId, Faye.URI.stringify(this.endpoint), message); |
|
1692 |
+ |
|
1693 |
+ if (!this.batching) return Faye.Promise.fulfilled(this.request([message])); |
|
1694 |
+ |
|
1695 |
+ this._outbox.push(message); |
|
1696 |
+ this._flushLargeBatch(); |
|
1697 |
+ this._promise = this._promise || new Faye.Promise(); |
|
1698 |
+ |
|
1699 |
+ if (message.channel === Faye.Channel.HANDSHAKE) { |
|
1700 |
+ this.addTimeout('publish', 0.01, this._flush, this); |
|
1701 |
+ return this._promise; |
|
1702 |
+ } |
|
1703 |
+ |
|
1704 |
+ if (message.channel === Faye.Channel.CONNECT) |
|
1705 |
+ this._connectMessage = message; |
|
1706 |
+ |
|
1707 |
+ this.addTimeout('publish', this.MAX_DELAY, this._flush, this); |
|
1708 |
+ return this._promise; |
|
1709 |
+ }, |
|
1710 |
+ |
|
1711 |
+ _flush: function() { |
|
1712 |
+ this.removeTimeout('publish'); |
|
1713 |
+ |
|
1714 |
+ if (this._outbox.length > 1 && this._connectMessage) |
|
1715 |
+ this._connectMessage.advice = {timeout: 0}; |
|
1716 |
+ |
|
1717 |
+ Faye.Promise.fulfill(this._promise, this.request(this._outbox)); |
|
1718 |
+ delete this._promise; |
|
1719 |
+ |
|
1720 |
+ this._connectMessage = null; |
|
1721 |
+ this._outbox = []; |
|
1722 |
+ }, |
|
1723 |
+ |
|
1724 |
+ _flushLargeBatch: function() { |
|
1725 |
+ var string = this.encode(this._outbox); |
|
1726 |
+ if (string.length < this._dispatcher.maxRequestSize) return; |
|
1727 |
+ var last = this._outbox.pop(); |
|
1728 |
+ this._flush(); |
|
1729 |
+ if (last) this._outbox.push(last); |
|
1730 |
+ }, |
|
1731 |
+ |
|
1732 |
+ _receive: function(replies) { |
|
1733 |
+ if (!replies) return; |
|
1734 |
+ replies = [].concat(replies); |
|
1735 |
+ |
|
1736 |
+ this.debug('Client ? received from ? via ?: ?', |
|
1737 |
+ this._dispatcher.clientId, Faye.URI.stringify(this.endpoint), this.connectionType, replies); |
|
1738 |
+ |
|
1739 |
+ for (var i = 0, n = replies.length; i < n; i++) |
|
1740 |
+ this._dispatcher.handleResponse(replies[i]); |
|
1741 |
+ }, |
|
1742 |
+ |
|
1743 |
+ _handleError: function(messages, immediate) { |
|
1744 |
+ messages = [].concat(messages); |
|
1745 |
+ |
|
1746 |
+ this.debug('Client ? failed to send to ? via ?: ?', |
|
1747 |
+ this._dispatcher.clientId, Faye.URI.stringify(this.endpoint), this.connectionType, messages); |
|
1748 |
+ |
|
1749 |
+ for (var i = 0, n = messages.length; i < n; i++) |
|
1750 |
+ this._dispatcher.handleError(messages[i]); |
|
1751 |
+ }, |
|
1752 |
+ |
|
1753 |
+ _getCookies: function() { |
|
1754 |
+ var cookies = this._dispatcher.cookies, |
|
1755 |
+ url = Faye.URI.stringify(this.endpoint); |
|
1756 |
+ |
|
1757 |
+ if (!cookies) return ''; |
|
1758 |
+ |
|
1759 |
+ return Faye.map(cookies.getCookiesSync(url), function(cookie) { |
|
1760 |
+ return cookie.cookieString(); |
|
1761 |
+ }).join('; '); |
|
1762 |
+ }, |
|
1763 |
+ |
|
1764 |
+ _storeCookies: function(setCookie) { |
|
1765 |
+ var cookies = this._dispatcher.cookies, |
|
1766 |
+ url = Faye.URI.stringify(this.endpoint), |
|
1767 |
+ cookie; |
|
1768 |
+ |
|
1769 |
+ if (!setCookie || !cookies) return; |
|
1770 |
+ setCookie = [].concat(setCookie); |
|
1771 |
+ |
|
1772 |
+ for (var i = 0, n = setCookie.length; i < n; i++) { |
|
1773 |
+ cookie = Faye.Cookies.Cookie.parse(setCookie[i]); |
|
1774 |
+ cookies.setCookieSync(cookie, url); |
|
1775 |
+ } |
|
1776 |
+ } |
|
1777 |
+ |
|
1778 |
+}), { |
|
1779 |
+ get: function(dispatcher, allowed, disabled, callback, context) { |
|
1780 |
+ var endpoint = dispatcher.endpoint; |
|
1781 |
+ |
|
1782 |
+ Faye.asyncEach(this._transports, function(pair, resume) { |
|
1783 |
+ var connType = pair[0], klass = pair[1], |
|
1784 |
+ connEndpoint = dispatcher.endpointFor(connType); |
|
1785 |
+ |
|
1786 |
+ if (Faye.indexOf(disabled, connType) >= 0) |
|
1787 |
+ return resume(); |
|
1788 |
+ |
|
1789 |
+ if (Faye.indexOf(allowed, connType) < 0) { |
|
1790 |
+ klass.isUsable(dispatcher, connEndpoint, function() {}); |
|
1791 |
+ return resume(); |
|
1792 |
+ } |
|
1793 |
+ |
|
1794 |
+ klass.isUsable(dispatcher, connEndpoint, function(isUsable) { |
|
1795 |
+ if (!isUsable) return resume(); |
|
1796 |
+ var transport = klass.hasOwnProperty('create') ? klass.create(dispatcher, connEndpoint) : new klass(dispatcher, connEndpoint); |
|
1797 |
+ callback.call(context, transport); |
|
1798 |
+ }); |
|
1799 |
+ }, function() { |
|
1800 |
+ throw new Error('Could not find a usable connection type for ' + Faye.URI.stringify(endpoint)); |
|
1801 |
+ }); |
|
1802 |
+ }, |
|
1803 |
+ |
|
1804 |
+ register: function(type, klass) { |
|
1805 |
+ this._transports.push([type, klass]); |
|
1806 |
+ klass.prototype.connectionType = type; |
|
1807 |
+ }, |
|
1808 |
+ |
|
1809 |
+ getConnectionTypes: function() { |
|
1810 |
+ return Faye.map(this._transports, function(t) { return t[0] }); |
|
1811 |
+ }, |
|
1812 |
+ |
|
1813 |
+ _transports: [] |
|
1814 |
+}); |
|
1815 |
+ |
|
1816 |
+Faye.extend(Faye.Transport.prototype, Faye.Logging); |
|
1817 |
+Faye.extend(Faye.Transport.prototype, Faye.Timeouts); |
|
1818 |
+ |
|
1819 |
+Faye.Event = { |
|
1820 |
+ _registry: [], |
|
1821 |
+ |
|
1822 |
+ on: function(element, eventName, callback, context) { |
|
1823 |
+ var wrapped = function() { callback.call(context) }; |
|
1824 |
+ |
|
1825 |
+ if (element.addEventListener) |
|
1826 |
+ element.addEventListener(eventName, wrapped, false); |
|
1827 |
+ else |
|
1828 |
+ element.attachEvent('on' + eventName, wrapped); |
|
1829 |
+ |
|
1830 |
+ this._registry.push({ |
|
1831 |
+ _element: element, |
|
1832 |
+ _type: eventName, |
|
1833 |
+ _callback: callback, |
|
1834 |
+ _context: context, |
|
1835 |
+ _handler: wrapped |
|
1836 |
+ }); |
|
1837 |
+ }, |
|
1838 |
+ |
|
1839 |
+ detach: function(element, eventName, callback, context) { |
|
1840 |
+ var i = this._registry.length, register; |
|
1841 |
+ while (i--) { |
|
1842 |
+ register = this._registry[i]; |
|
1843 |
+ |
|
1844 |
+ if ((element && element !== register._element) || |
|
1845 |
+ (eventName && eventName !== register._type) || |
|
1846 |
+ (callback && callback !== register._callback) || |
|
1847 |
+ (context && context !== register._context)) |
|
1848 |
+ continue; |
|
1849 |
+ |
|
1850 |
+ if (register._element.removeEventListener) |
|
1851 |
+ register._element.removeEventListener(register._type, register._handler, false); |
|
1852 |
+ else |
|
1853 |
+ register._element.detachEvent('on' + register._type, register._handler); |
|
1854 |
+ |
|
1855 |
+ this._registry.splice(i,1); |
|
1856 |
+ register = null; |
|
1857 |
+ } |
|
1858 |
+ } |
|
1859 |
+}; |
|
1860 |
+ |
|
1861 |
+if (Faye.ENV.onunload !== undefined) Faye.Event.on(Faye.ENV, 'unload', Faye.Event.detach, Faye.Event); |
|
1862 |
+ |
|
1863 |
+/* |
|
1864 |
+ json2.js |
|
1865 |
+ 2013-05-26 |
|
1866 |
+ |
|
1867 |
+ Public Domain. |
|
1868 |
+ |
|
1869 |
+ NO WARRANTY EXPRESSED OR IMPLIED. USE AT YOUR OWN RISK. |
|
1870 |
+ |
|
1871 |
+ See http://www.JSON.org/js.html |
|
1872 |
+ |
|
1873 |
+ |
|
1874 |
+ This code should be minified before deployment. |
|
1875 |
+ See http://javascript.crockford.com/jsmin.html |
|
1876 |
+ |
|
1877 |
+ USE YOUR OWN COPY. IT IS EXTREMELY UNWISE TO LOAD CODE FROM SERVERS YOU DO |
|
1878 |
+ NOT CONTROL. |
|
1879 |
+ |
|
1880 |
+ |
|
1881 |
+ This file creates a global JSON object containing two methods: stringify |
|
1882 |
+ and parse. |
|
1883 |
+ |
|
1884 |
+ JSON.stringify(value, replacer, space) |
|
1885 |
+ value any JavaScript value, usually an object or array. |
|
1886 |
+ |
|
1887 |
+ replacer an optional parameter that determines how object |
|
1888 |
+ values are stringified for objects. It can be a |
|
1889 |
+ function or an array of strings. |
|
1890 |
+ |
|
1891 |
+ space an optional parameter that specifies the indentation |
|
1892 |
+ of nested structures. If it is omitted, the text will |
|
1893 |
+ be packed without extra whitespace. If it is a number, |
|
1894 |
+ it will specify the number of spaces to indent at each |
|
1895 |
+ level. If it is a string (such as '\t' or ' '), |
|
1896 |
+ it contains the characters used to indent at each level. |
|
1897 |
+ |
|
1898 |
+ This method produces a JSON text from a JavaScript value. |
|
1899 |
+ |
|
1900 |
+ When an object value is found, if the object contains a toJSON |
|
1901 |
+ method, its toJSON method will be called and the result will be |
|
1902 |
+ stringified. A toJSON method does not serialize: it returns the |
|
1903 |
+ value represented by the name/value pair that should be serialized, |
|
1904 |
+ or undefined if nothing should be serialized. The toJSON method |
|
1905 |
+ will be passed the key associated with the value, and this will be |
|
1906 |
+ bound to the value |
|
1907 |
+ |
|
1908 |
+ For example, this would serialize Dates as ISO strings. |
|
1909 |
+ |
|
1910 |
+ Date.prototype.toJSON = function (key) { |
|
1911 |
+ function f(n) { |
|
1912 |
+ // Format integers to have at least two digits. |
|
1913 |
+ return n < 10 ? '0' + n : n; |
|
1914 |
+ } |
|
1915 |
+ |
|
1916 |
+ return this.getUTCFullYear() + '-' + |
|
1917 |
+ f(this.getUTCMonth() + 1) + '-' + |
|
1918 |
+ f(this.getUTCDate()) + 'T' + |
|
1919 |
+ f(this.getUTCHours()) + ':' + |
|
1920 |
+ f(this.getUTCMinutes()) + ':' + |
|
1921 |
+ f(this.getUTCSeconds()) + 'Z'; |
|
1922 |
+ }; |
|
1923 |
+ |
|
1924 |
+ You can provide an optional replacer method. It will be passed the |
|
1925 |
+ key and value of each member, with this bound to the containing |
|
1926 |
+ object. The value that is returned from your method will be |
|
1927 |
+ serialized. If your method returns undefined, then the member will |
|
1928 |
+ be excluded from the serialization. |
|
1929 |
+ |
|
1930 |
+ If the replacer parameter is an array of strings, then it will be |
|
1931 |
+ used to select the members to be serialized. It filters the results |
|
1932 |
+ such that only members with keys listed in the replacer array are |
|
1933 |
+ stringified. |
|
1934 |
+ |
|
1935 |
+ Values that do not have JSON representations, such as undefined or |
|
1936 |
+ functions, will not be serialized. Such values in objects will be |
|
1937 |
+ dropped; in arrays they will be replaced with null. You can use |
|
1938 |
+ a replacer function to replace those with JSON values. |
|
1939 |
+ JSON.stringify(undefined) returns undefined. |
|
1940 |
+ |
|
1941 |
+ The optional space parameter produces a stringification of the |
|
1942 |
+ value that is filled with line breaks and indentation to make it |
|
1943 |
+ easier to read. |
|
1944 |
+ |
|
1945 |
+ If the space parameter is a non-empty string, then that string will |
|
1946 |
+ be used for indentation. If the space parameter is a number, then |
|
1947 |
+ the indentation will be that many spaces. |
|
1948 |
+ |
|
1949 |
+ Example: |
|
1950 |
+ |
|
1951 |
+ text = JSON.stringify(['e', {pluribus: 'unum'}]); |
|
1952 |
+ // text is '["e",{"pluribus":"unum"}]' |
|
1953 |
+ |
|
1954 |
+ |
|
1955 |
+ text = JSON.stringify(['e', {pluribus: 'unum'}], null, '\t'); |
|
1956 |
+ // text is '[\n\t"e",\n\t{\n\t\t"pluribus": "unum"\n\t}\n]' |
|
1957 |
+ |
|
1958 |
+ text = JSON.stringify([new Date()], function (key, value) { |
|
1959 |
+ return this[key] instanceof Date ? |
|
1960 |
+ 'Date(' + this[key] + ')' : value; |
|
1961 |
+ }); |
|
1962 |
+ // text is '["Date(---current time---)"]' |
|
1963 |
+ |
|
1964 |
+ |
|
1965 |
+ JSON.parse(text, reviver) |
|
1966 |
+ This method parses a JSON text to produce an object or array. |
|
1967 |
+ It can throw a SyntaxError exception. |
|
1968 |
+ |
|
1969 |
+ The optional reviver parameter is a function that can filter and |
|
1970 |
+ transform the results. It receives each of the keys and values, |
|
1971 |
+ and its return value is used instead of the original value. |
|
1972 |
+ If it returns what it received, then the structure is not modified. |
|
1973 |
+ If it returns undefined then the member is deleted. |
|
1974 |
+ |
|
1975 |
+ Example: |
|
1976 |
+ |
|
1977 |
+ // Parse the text. Values that look like ISO date strings will |
|
1978 |
+ // be converted to Date objects. |
|
1979 |
+ |
|
1980 |
+ myData = JSON.parse(text, function (key, value) { |
|
1981 |
+ var a; |
|
1982 |
+ if (typeof value === 'string') { |
|
1983 |
+ a = |
|
1984 |
+/^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2}(?:\.\d*)?)Z$/.exec(value); |
|
1985 |
+ if (a) { |
|
1986 |
+ return new Date(Date.UTC(+a[1], +a[2] - 1, +a[3], +a[4], |
|
1987 |
+ +a[5], +a[6])); |
|
1988 |
+ } |
|
1989 |
+ } |
|
1990 |
+ return value; |
|
1991 |
+ }); |
|
1992 |
+ |
|
1993 |
+ myData = JSON.parse('["Date(09/09/2001)"]', function (key, value) { |
|
1994 |
+ var d; |
|
1995 |
+ if (typeof value === 'string' && |
|
1996 |
+ value.slice(0, 5) === 'Date(' && |
|
1997 |
+ value.slice(-1) === ')') { |
|
1998 |
+ d = new Date(value.slice(5, -1)); |
|
1999 |
+ if (d) { |
|
2000 |
+ return d; |
|
2001 |
+ } |
|
2002 |
+ } |
|
2003 |
+ return value; |
|
2004 |
+ }); |
|
2005 |
+ |
|
2006 |
+ |
|
2007 |
+ This is a reference implementation. You are free to copy, modify, or |
|
2008 |
+ redistribute. |
|
2009 |
+*/ |
|
2010 |
+ |
|
2011 |
+/*jslint evil: true, regexp: true */ |
|
2012 |
+ |
|
2013 |
+/*members "", "\b", "\t", "\n", "\f", "\r", "\"", JSON, "\\", apply, |
|
2014 |
+ call, charCodeAt, getUTCDate, getUTCFullYear, getUTCHours, |
|
2015 |
+ getUTCMinutes, getUTCMonth, getUTCSeconds, hasOwnProperty, join, |
|
2016 |
+ lastIndex, length, parse, prototype, push, replace, slice, stringify, |
|
2017 |
+ test, toJSON, toString, valueOf |
|
2018 |
+*/ |
|
2019 |
+ |
|
2020 |
+ |
|
2021 |
+// Create a JSON object only if one does not already exist. We create the |
|
2022 |
+// methods in a closure to avoid creating global variables. |
|
2023 |
+ |
|
2024 |
+if (typeof JSON !== 'object') { |
|
2025 |
+ JSON = {}; |
|
2026 |
+} |
|
2027 |
+ |
|
2028 |
+(function () { |
|
2029 |
+ 'use strict'; |
|
2030 |
+ |
|
2031 |
+ function f(n) { |
|
2032 |
+ // Format integers to have at least two digits. |
|
2033 |
+ return n < 10 ? '0' + n : n; |
|
2034 |
+ } |
|
2035 |
+ |
|
2036 |
+ if (typeof Date.prototype.toJSON !== 'function') { |
|
2037 |
+ |
|
2038 |
+ Date.prototype.toJSON = function () { |
|
2039 |
+ |
|
2040 |
+ return isFinite(this.valueOf()) |
|
2041 |
+ ? this.getUTCFullYear() + '-' + |
|
2042 |
+ f(this.getUTCMonth() + 1) + '-' + |
|
2043 |
+ f(this.getUTCDate()) + 'T' + |
|
2044 |
+ f(this.getUTCHours()) + ':' + |
|
2045 |
+ f(this.getUTCMinutes()) + ':' + |
|
2046 |
+ f(this.getUTCSeconds()) + 'Z' |
|
2047 |
+ : null; |
|
2048 |
+ }; |
|
2049 |
+ |
|
2050 |
+ String.prototype.toJSON = |
|
2051 |
+ Number.prototype.toJSON = |
|
2052 |
+ Boolean.prototype.toJSON = function () { |
|
2053 |
+ return this.valueOf(); |
|
2054 |
+ }; |
|
2055 |
+ } |
|
2056 |
+ |
|
2057 |
+ var cx = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g, |
|
2058 |
+ escapable = /[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g, |
|
2059 |
+ gap, |
|
2060 |
+ indent, |
|
2061 |
+ meta = { // table of character substitutions |
|
2062 |
+ '\b': '\\b', |
|
2063 |
+ '\t': '\\t', |
|
2064 |
+ '\n': '\\n', |
|
2065 |
+ '\f': '\\f', |
|
2066 |
+ '\r': '\\r', |
|
2067 |
+ '"' : '\\"', |
|
2068 |
+ '\\': '\\\\' |
|
2069 |
+ }, |
|
2070 |
+ rep; |
|
2071 |
+ |
|
2072 |
+ |
|
2073 |
+ function quote(string) { |
|
2074 |
+ |
|
2075 |
+// If the string contains no control characters, no quote characters, and no |
|
2076 |
+// backslash characters, then we can safely slap some quotes around it. |
|
2077 |
+// Otherwise we must also replace the offending characters with safe escape |
|
2078 |
+// sequences. |
|
2079 |
+ |
|
2080 |
+ escapable.lastIndex = 0; |
|
2081 |
+ return escapable.test(string) ? '"' + string.replace(escapable, function (a) { |
|
2082 |
+ var c = meta[a]; |
|
2083 |
+ return typeof c === 'string' |
|
2084 |
+ ? c |
|
2085 |
+ : '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4); |
|
2086 |
+ }) + '"' : '"' + string + '"'; |
|
2087 |
+ } |
|
2088 |
+ |
|
2089 |
+ |
|
2090 |
+ function str(key, holder) { |
|
2091 |
+ |
|
2092 |
+// Produce a string from holder[key]. |
|
2093 |
+ |
|
2094 |
+ var i, // The loop counter. |
|
2095 |
+ k, // The member key. |
|
2096 |
+ v, // The member value. |
|
2097 |
+ length, |
|
2098 |
+ mind = gap, |
|
2099 |
+ partial, |
|
2100 |
+ value = holder[key]; |
|
2101 |
+ |
|
2102 |
+// If the value has a toJSON method, call it to obtain a replacement value. |
|
2103 |
+ |
|
2104 |
+ if (value && typeof value === 'object' && |
|
2105 |
+ typeof value.toJSON === 'function') { |
|
2106 |
+ value = value.toJSON(key); |
|
2107 |
+ } |
|
2108 |
+ |
|
2109 |
+// If we were called with a replacer function, then call the replacer to |
|
2110 |
+// obtain a replacement value. |
|
2111 |
+ |
|
2112 |
+ if (typeof rep === 'function') { |
|
2113 |
+ value = rep.call(holder, key, value); |
|
2114 |
+ } |
|
2115 |
+ |
|
2116 |
+// What happens next depends on the value's type. |
|
2117 |
+ |
|
2118 |
+ switch (typeof value) { |
|
2119 |
+ case 'string': |
|
2120 |
+ return quote(value); |
|
2121 |
+ |
|
2122 |
+ case 'number': |
|
2123 |
+ |
|
2124 |
+// JSON numbers must be finite. Encode non-finite numbers as null. |
|
2125 |
+ |
|
2126 |
+ return isFinite(value) ? String(value) : 'null'; |
|
2127 |
+ |
|
2128 |
+ case 'boolean': |
|
2129 |
+ case 'null': |
|
2130 |
+ |
|
2131 |
+// If the value is a boolean or null, convert it to a string. Note: |
|
2132 |
+// typeof null does not produce 'null'. The case is included here in |
|
2133 |
+// the remote chance that this gets fixed someday. |
|
2134 |
+ |
|
2135 |
+ return String(value); |
|
2136 |
+ |
|
2137 |
+// If the type is 'object', we might be dealing with an object or an array or |
|
2138 |
+// null. |
|
2139 |
+ |
|
2140 |
+ case 'object': |
|
2141 |
+ |
|
2142 |
+// Due to a specification blunder in ECMAScript, typeof null is 'object', |
|
2143 |
+// so watch out for that case. |
|
2144 |
+ |
|
2145 |
+ if (!value) { |
|
2146 |
+ return 'null'; |
|
2147 |
+ } |
|
2148 |
+ |
|
2149 |
+// Make an array to hold the partial results of stringifying this object value. |
|
2150 |
+ |
|
2151 |
+ gap += indent; |
|
2152 |
+ partial = []; |
|
2153 |
+ |
|
2154 |
+// Is the value an array? |
|
2155 |
+ |
|
2156 |
+ if (Object.prototype.toString.apply(value) === '[object Array]') { |
|
2157 |
+ |
|
2158 |
+// The value is an array. Stringify every element. Use null as a placeholder |
|
2159 |
+// for non-JSON values. |
|
2160 |
+ |
|
2161 |
+ length = value.length; |
|
2162 |
+ for (i = 0; i < length; i += 1) { |
|
2163 |
+ partial[i] = str(i, value) || 'null'; |
|
2164 |
+ } |
|
2165 |
+ |
|
2166 |
+// Join all of the elements together, separated with commas, and wrap them in |
|
2167 |
+// brackets. |
|
2168 |
+ |
|
2169 |
+ v = partial.length === 0 |
|
2170 |
+ ? '[]' |
|
2171 |
+ : gap |
|
2172 |
+ ? '[\n' + gap + partial.join(',\n' + gap) + '\n' + mind + ']' |
|
2173 |
+ : '[' + partial.join(',') + ']'; |
|
2174 |
+ gap = mind; |
|
2175 |
+ return v; |
|
2176 |
+ } |
|
2177 |
+ |
|
2178 |
+// If the replacer is an array, use it to select the members to be stringified. |
|
2179 |
+ |
|
2180 |
+ if (rep && typeof rep === 'object') { |
|
2181 |
+ length = rep.length; |
|
2182 |
+ for (i = 0; i < length; i += 1) { |
|
2183 |
+ if (typeof rep[i] === 'string') { |
|
2184 |
+ k = rep[i]; |
|
2185 |
+ v = str(k, value); |
|
2186 |
+ if (v) { |
|
2187 |
+ partial.push(quote(k) + (gap ? ': ' : ':') + v); |
|
2188 |
+ } |
|
2189 |
+ } |
|
2190 |
+ } |
|
2191 |
+ } else { |
|
2192 |
+ |
|
2193 |
+// Otherwise, iterate through all of the keys in the object. |
|
2194 |
+ |
|
2195 |
+ for (k in value) { |
|
2196 |
+ if (Object.prototype.hasOwnProperty.call(value, k)) { |
|
2197 |
+ v = str(k, value); |
|
2198 |
+ if (v) { |
|
2199 |
+ partial.push(quote(k) + (gap ? ': ' : ':') + v); |
|
2200 |
+ } |
|
2201 |
+ } |
|
2202 |
+ } |
|
2203 |
+ } |
|
2204 |
+ |
|
2205 |
+// Join all of the member texts together, separated with commas, |
|
2206 |
+// and wrap them in braces. |
|
2207 |
+ |
|
2208 |
+ v = partial.length === 0 |
|
2209 |
+ ? '{}' |
|
2210 |
+ : gap |
|
2211 |
+ ? '{\n' + gap + partial.join(',\n' + gap) + '\n' + mind + '}' |
|
2212 |
+ : '{' + partial.join(',') + '}'; |
|
2213 |
+ gap = mind; |
|
2214 |
+ return v; |
|
2215 |
+ } |
|
2216 |
+ } |
|
2217 |
+ |
|
2218 |
+// If the JSON object does not yet have a stringify method, give it one. |
|
2219 |
+ |
|
2220 |
+ Faye.stringify = function (value, replacer, space) { |
|
2221 |
+ |
|
2222 |
+// The stringify method takes a value and an optional replacer, and an optional |
|
2223 |
+// space parameter, and returns a JSON text. The replacer can be a function |
|
2224 |
+// that can replace values, or an array of strings that will select the keys. |
|
2225 |
+// A default replacer method can be provided. Use of the space parameter can |
|
2226 |
+// produce text that is more easily readable. |
|
2227 |
+ |
|
2228 |
+ var i; |
|
2229 |
+ gap = ''; |
|
2230 |
+ indent = ''; |
|
2231 |
+ |
|
2232 |
+// If the space parameter is a number, make an indent string containing that |
|
2233 |
+// many spaces. |
|
2234 |
+ |
|
2235 |
+ if (typeof space === 'number') { |
|
2236 |
+ for (i = 0; i < space; i += 1) { |
|
2237 |
+ indent += ' '; |
|
2238 |
+ } |
|
2239 |
+ |
|
2240 |
+// If the space parameter is a string, it will be used as the indent string. |
|
2241 |
+ |
|
2242 |
+ } else if (typeof space === 'string') { |
|
2243 |
+ indent = space; |
|
2244 |
+ } |
|
2245 |
+ |
|
2246 |
+// If there is a replacer, it must be a function or an array. |
|
2247 |
+// Otherwise, throw an error. |
|
2248 |
+ |
|
2249 |
+ rep = replacer; |
|
2250 |
+ if (replacer && typeof replacer !== 'function' && |
|
2251 |
+ (typeof replacer !== 'object' || |
|
2252 |
+ typeof replacer.length !== 'number')) { |
|
2253 |
+ throw new Error('JSON.stringify'); |
|
2254 |
+ } |
|
2255 |
+ |
|
2256 |
+// Make a fake root object containing our value under the key of ''. |
|
2257 |
+// Return the result of stringifying the value. |
|
2258 |
+ |
|
2259 |
+ return str('', {'': value}); |
|
2260 |
+ }; |
|
2261 |
+ |
|
2262 |
+ if (typeof JSON.stringify !== 'function') { |
|
2263 |
+ JSON.stringify = Faye.stringify; |
|
2264 |
+ } |
|
2265 |
+ |
|
2266 |
+// If the JSON object does not yet have a parse method, give it one. |
|
2267 |
+ |
|
2268 |
+ if (typeof JSON.parse !== 'function') { |
|
2269 |
+ JSON.parse = function (text, reviver) { |
|
2270 |
+ |
|
2271 |
+// The parse method takes a text and an optional reviver function, and returns |
|
2272 |
+// a JavaScript value if the text is a valid JSON text. |
|
2273 |
+ |
|
2274 |
+ var j; |
|
2275 |
+ |
|
2276 |
+ function walk(holder, key) { |
|
2277 |
+ |
|
2278 |
+// The walk method is used to recursively walk the resulting structure so |
|
2279 |
+// that modifications can be made. |
|
2280 |
+ |
|
2281 |
+ var k, v, value = holder[key]; |
|
2282 |
+ if (value && typeof value === 'object') { |
|
2283 |
+ for (k in value) { |
|
2284 |
+ if (Object.prototype.hasOwnProperty.call(value, k)) { |
|
2285 |
+ v = walk(value, k); |
|
2286 |
+ if (v !== undefined) { |
|
2287 |
+ value[k] = v; |
|
2288 |
+ } else { |
|
2289 |
+ delete value[k]; |
|
2290 |
+ } |
|
2291 |
+ } |
|
2292 |
+ } |
|
2293 |
+ } |
|
2294 |
+ return reviver.call(holder, key, value); |
|
2295 |
+ } |
|
2296 |
+ |
|
2297 |
+ |
|
2298 |
+// Parsing happens in four stages. In the first stage, we replace certain |
|
2299 |
+// Unicode characters with escape sequences. JavaScript handles many characters |
|
2300 |
+// incorrectly, either silently deleting them, or treating them as line endings. |
|
2301 |
+ |
|
2302 |
+ text = String(text); |
|
2303 |
+ cx.lastIndex = 0; |
|
2304 |
+ if (cx.test(text)) { |
|
2305 |
+ text = text.replace(cx, function (a) { |
|
2306 |
+ return '\\u' + |
|
2307 |
+ ('0000' + a.charCodeAt(0).toString(16)).slice(-4); |
|
2308 |
+ }); |
|
2309 |
+ } |
|
2310 |
+ |
|
2311 |
+// In the second stage, we run the text against regular expressions that look |
|
2312 |
+// for non-JSON patterns. We are especially concerned with '()' and 'new' |
|
2313 |
+// because they can cause invocation, and '=' because it can cause mutation. |
|
2314 |
+// But just to be safe, we want to reject all unexpected forms. |
|
2315 |
+ |
|
2316 |
+// We split the second stage into 4 regexp operations in order to work around |
|
2317 |
+// crippling inefficiencies in IE's and Safari's regexp engines. First we |
|
2318 |
+// replace the JSON backslash pairs with '@' (a non-JSON character). Second, we |
|
2319 |
+// replace all simple value tokens with ']' characters. Third, we delete all |
|
2320 |
+// open brackets that follow a colon or comma or that begin the text. Finally, |
|
2321 |
+// we look to see that the remaining characters are only whitespace or ']' or |
|
2322 |
+// ',' or ':' or '{' or '}'. If that is so, then the text is safe for eval. |
|
2323 |
+ |
|
2324 |
+ if (/^[\],:{}\s]*$/ |
|
2325 |
+ .test(text.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, '@') |
|
2326 |
+ .replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']') |
|
2327 |
+ .replace(/(?:^|:|,)(?:\s*\[)+/g, ''))) { |
|
2328 |
+ |
|
2329 |
+// In the third stage we use the eval function to compile the text into a |
|
2330 |
+// JavaScript structure. The '{' operator is subject to a syntactic ambiguity |
|
2331 |
+// in JavaScript: it can begin a block or an object literal. We wrap the text |
|
2332 |
+// in parens to eliminate the ambiguity. |
|
2333 |
+ |
|
2334 |
+ j = eval('(' + text + ')'); |
|
2335 |
+ |
|
2336 |
+// In the optional fourth stage, we recursively walk the new structure, passing |
|
2337 |
+// each name/value pair to a reviver function for possible transformation. |
|
2338 |
+ |
|
2339 |
+ return typeof reviver === 'function' |
|
2340 |
+ ? walk({'': j}, '') |
|
2341 |
+ : j; |
|
2342 |
+ } |
|
2343 |
+ |
|
2344 |
+// If the text is not JSON parseable, then a SyntaxError is thrown. |
|
2345 |
+ |
|
2346 |
+ throw new SyntaxError('JSON.parse'); |
|
2347 |
+ }; |
|
2348 |
+ } |
|
2349 |
+}()); |
|
2350 |
+ |
|
2351 |
+Faye.Transport.WebSocket = Faye.extend(Faye.Class(Faye.Transport, { |
|
2352 |
+ UNCONNECTED: 1, |
|
2353 |
+ CONNECTING: 2, |
|
2354 |
+ CONNECTED: 3, |
|
2355 |
+ |
|
2356 |
+ batching: false, |
|
2357 |
+ |
|
2358 |
+ isUsable: function(callback, context) { |
|
2359 |
+ this.callback(function() { callback.call(context, true) }); |
|
2360 |
+ this.errback(function() { callback.call(context, false) }); |
|
2361 |
+ this.connect(); |
|
2362 |
+ }, |
|
2363 |
+ |
|
2364 |
+ request: function(messages) { |
|
2365 |
+ this._pending = this._pending || new Faye.Set(); |
|
2366 |
+ for (var i = 0, n = messages.length; i < n; i++) this._pending.add(messages[i]); |
|
2367 |
+ |
|
2368 |
+ var promise = new Faye.Promise(); |
|
2369 |
+ |
|
2370 |
+ this.callback(function(socket) { |
|
2371 |
+ if (!socket) return; |
|
2372 |
+ socket.send(Faye.toJSON(messages)); |
|
2373 |
+ Faye.Promise.fulfill(promise, socket); |
|
2374 |
+ }, this); |
|
2375 |
+ |
|
2376 |
+ this.connect(); |
|
2377 |
+ |
|
2378 |
+ return { |
|
2379 |
+ abort: function() { promise.then(function(ws) { ws.close() }) } |
|
2380 |
+ }; |
|
2381 |
+ }, |
|
2382 |
+ |
|
2383 |
+ connect: function() { |
|
2384 |
+ if (Faye.Transport.WebSocket._unloaded) return; |
|
2385 |
+ |
|
2386 |
+ this._state = this._state || this.UNCONNECTED; |
|
2387 |
+ if (this._state !== this.UNCONNECTED) return; |
|
2388 |
+ this._state = this.CONNECTING; |
|
2389 |
+ |
|
2390 |
+ var socket = this._createSocket(); |
|
2391 |
+ if (!socket) return this.setDeferredStatus('failed'); |
|
2392 |
+ |
|
2393 |
+ var self = this; |
|
2394 |
+ |
|
2395 |
+ socket.onopen = function() { |
|
2396 |
+ if (socket.headers) self._storeCookies(socket.headers['set-cookie']); |
|
2397 |
+ self._socket = socket; |
|
2398 |
+ self._state = self.CONNECTED; |
|
2399 |
+ self._everConnected = true; |
|
2400 |
+ self._ping(); |
|
2401 |
+ self.setDeferredStatus('succeeded', socket); |
|
2402 |
+ }; |
|
2403 |
+ |
|
2404 |
+ var closed = false; |
|
2405 |
+ socket.onclose = socket.onerror = function() { |
|
2406 |
+ if (closed) return; |
|
2407 |
+ closed = true; |
|
2408 |
+ |
|
2409 |
+ var wasConnected = (self._state === self.CONNECTED); |
|
2410 |
+ socket.onopen = socket.onclose = socket.onerror = socket.onmessage = null; |
|
2411 |
+ |
|
2412 |
+ delete self._socket; |
|
2413 |
+ self._state = self.UNCONNECTED; |
|
2414 |
+ self.removeTimeout('ping'); |
|
2415 |
+ self.setDeferredStatus('unknown'); |
|
2416 |
+ |
|
2417 |
+ var pending = self._pending ? self._pending.toArray() : []; |
|
2418 |
+ delete self._pending; |
|
2419 |
+ |
|
2420 |
+ if (wasConnected) { |
|
2421 |
+ self._handleError(pending, true); |
|
2422 |
+ } else if (self._everConnected) { |
|
2423 |
+ self._handleError(pending); |
|
2424 |
+ } else { |
|
2425 |
+ self.setDeferredStatus('failed'); |
|
2426 |
+ } |
|
2427 |
+ }; |
|
2428 |
+ |
|
2429 |
+ socket.onmessage = function(event) { |
|
2430 |
+ var replies = JSON.parse(event.data); |
|
2431 |
+ if (!replies) return; |
|
2432 |
+ |
|
2433 |
+ replies = [].concat(replies); |
|
2434 |
+ |
|
2435 |
+ for (var i = 0, n = replies.length; i < n; i++) { |
|
2436 |
+ if (replies[i].successful === undefined) continue; |
|
2437 |
+ self._pending.remove(replies[i]); |
|
2438 |
+ } |
|
2439 |
+ self._receive(replies); |
|
2440 |
+ }; |
|
2441 |
+ }, |
|
2442 |
+ |
|
2443 |
+ close: function() { |
|
2444 |
+ if (!this._socket) return; |
|
2445 |
+ this._socket.close(); |
|
2446 |
+ }, |
|
2447 |
+ |
|
2448 |
+ _createSocket: function() { |
|
2449 |
+ var url = Faye.Transport.WebSocket.getSocketUrl(this.endpoint), |
|
2450 |
+ headers = this._dispatcher.headers, |
|
2451 |
+ extensions = this._dispatcher.wsExtensions, |
|
2452 |
+ cookie = this._getCookies(), |
|
2453 |
+ tls = this._dispatcher.tls, |
|
2454 |
+ options = {extensions: extensions, headers: headers, proxy: this._proxy, tls: tls}; |
|
2455 |
+ |
|
2456 |
+ if (cookie !== '') options.headers['Cookie'] = cookie; |
|
2457 |
+ |
|
2458 |
+ if (Faye.WebSocket) return new Faye.WebSocket.Client(url, [], options); |
|
2459 |
+ if (Faye.ENV.MozWebSocket) return new MozWebSocket(url); |
|
2460 |
+ if (Faye.ENV.WebSocket) return new WebSocket(url); |
|
2461 |
+ }, |
|
2462 |
+ |
|
2463 |
+ _ping: function() { |
|
2464 |
+ if (!this._socket) return; |
|
2465 |
+ this._socket.send('[]'); |
|
2466 |
+ this.addTimeout('ping', this._dispatcher.timeout / 2, this._ping, this); |
|
2467 |
+ } |
|
2468 |
+ |
|
2469 |
+}), { |
|
2470 |
+ PROTOCOLS: { |
|
2471 |
+ 'http:': 'ws:', |
|
2472 |
+ 'https:': 'wss:' |
|
2473 |
+ }, |
|
2474 |
+ |
|
2475 |
+ create: function(dispatcher, endpoint) { |
|
2476 |
+ var sockets = dispatcher.transports.websocket = dispatcher.transports.websocket || {}; |
|
2477 |
+ sockets[endpoint.href] = sockets[endpoint.href] || new this(dispatcher, endpoint); |
|
2478 |
+ return sockets[endpoint.href]; |
|
2479 |
+ }, |
|
2480 |
+ |
|
2481 |
+ getSocketUrl: function(endpoint) { |
|
2482 |
+ endpoint = Faye.copyObject(endpoint); |
|
2483 |
+ endpoint.protocol = this.PROTOCOLS[endpoint.protocol]; |
|
2484 |
+ return Faye.URI.stringify(endpoint); |
|
2485 |
+ }, |
|
2486 |
+ |
|
2487 |
+ isUsable: function(dispatcher, endpoint, callback, context) { |
|
2488 |
+ this.create(dispatcher, endpoint).isUsable(callback, context); |
|
2489 |
+ } |
|
2490 |
+}); |
|
2491 |
+ |
|
2492 |
+Faye.extend(Faye.Transport.WebSocket.prototype, Faye.Deferrable); |
|
2493 |
+Faye.Transport.register('websocket', Faye.Transport.WebSocket); |
|
2494 |
+ |
|
2495 |
+if (Faye.Event && Faye.ENV.onbeforeunload !== undefined) |
|
2496 |
+ Faye.Event.on(Faye.ENV, 'beforeunload', function() { |
|
2497 |
+ Faye.Transport.WebSocket._unloaded = true; |
|
2498 |
+ }); |
|
2499 |
+ |
|
2500 |
+Faye.Transport.EventSource = Faye.extend(Faye.Class(Faye.Transport, { |
|
2501 |
+ initialize: function(dispatcher, endpoint) { |
|
2502 |
+ Faye.Transport.prototype.initialize.call(this, dispatcher, endpoint); |
|
2503 |
+ if (!Faye.ENV.EventSource) return this.setDeferredStatus('failed'); |
|
2504 |
+ |
|
2505 |
+ this._xhr = new Faye.Transport.XHR(dispatcher, endpoint); |
|
2506 |
+ |
|
2507 |
+ endpoint = Faye.copyObject(endpoint); |
|
2508 |
+ endpoint.pathname += '/' + dispatcher.clientId; |
|
2509 |
+ |
|
2510 |
+ var socket = new EventSource(Faye.URI.stringify(endpoint)), |
|
2511 |
+ self = this; |
|
2512 |
+ |
|
2513 |
+ socket.onopen = function() { |
|
2514 |
+ self._everConnected = true; |
|
2515 |
+ self.setDeferredStatus('succeeded'); |
|
2516 |
+ }; |
|
2517 |
+ |
|
2518 |
+ socket.onerror = function() { |
|
2519 |
+ if (self._everConnected) { |
|
2520 |
+ self._handleError([]); |
|
2521 |
+ } else { |
|
2522 |
+ self.setDeferredStatus('failed'); |
|
2523 |
+ socket.close(); |
|
2524 |
+ } |
|
2525 |
+ }; |
|
2526 |
+ |
|
2527 |
+ socket.onmessage = function(event) { |
|
2528 |
+ self._receive(JSON.parse(event.data)); |
|
2529 |
+ }; |
|
2530 |
+ |
|
2531 |
+ this._socket = socket; |
|
2532 |
+ }, |
|
2533 |
+ |
|
2534 |
+ close: function() { |
|
2535 |
+ if (!this._socket) return; |
|
2536 |
+ this._socket.onopen = this._socket.onerror = this._socket.onmessage = null; |
|
2537 |
+ this._socket.close(); |
|
2538 |
+ delete this._socket; |
|
2539 |
+ }, |
|
2540 |
+ |
|
2541 |
+ isUsable: function(callback, context) { |
|
2542 |
+ this.callback(function() { callback.call(context, true) }); |
|
2543 |
+ this.errback(function() { callback.call(context, false) }); |
|
2544 |
+ }, |
|
2545 |
+ |
|
2546 |
+ encode: function(messages) { |
|
2547 |
+ return this._xhr.encode(messages); |
|
2548 |
+ }, |
|
2549 |
+ |
|
2550 |
+ request: function(messages) { |
|
2551 |
+ return this._xhr.request(messages); |
|
2552 |
+ } |
|
2553 |
+ |
|
2554 |
+}), { |
|
2555 |
+ isUsable: function(dispatcher, endpoint, callback, context) { |
|
2556 |
+ var id = dispatcher.clientId; |
|
2557 |
+ if (!id) return callback.call(context, false); |
|
2558 |
+ |
|
2559 |
+ Faye.Transport.XHR.isUsable(dispatcher, endpoint, function(usable) { |
|
2560 |
+ if (!usable) return callback.call(context, false); |
|
2561 |
+ this.create(dispatcher, endpoint).isUsable(callback, context); |
|
2562 |
+ }, this); |
|
2563 |
+ }, |
|
2564 |
+ |
|
2565 |
+ create: function(dispatcher, endpoint) { |
|
2566 |
+ var sockets = dispatcher.transports.eventsource = dispatcher.transports.eventsource || {}, |
|
2567 |
+ id = dispatcher.clientId; |
|
2568 |
+ |
|
2569 |
+ var url = Faye.copyObject(endpoint); |
|
2570 |
+ url.pathname += '/' + (id || ''); |
|
2571 |
+ url = Faye.URI.stringify(url); |
|
2572 |
+ |
|
2573 |
+ sockets[url] = sockets[url] || new this(dispatcher, endpoint); |
|
2574 |
+ return sockets[url]; |
|
2575 |
+ } |
|
2576 |
+}); |
|
2577 |
+ |
|
2578 |
+Faye.extend(Faye.Transport.EventSource.prototype, Faye.Deferrable); |
|
2579 |
+Faye.Transport.register('eventsource', Faye.Transport.EventSource); |
|
2580 |
+ |
|
2581 |
+Faye.Transport.XHR = Faye.extend(Faye.Class(Faye.Transport, { |
|
2582 |
+ encode: function(messages) { |
|
2583 |
+ return Faye.toJSON(messages); |
|
2584 |
+ }, |
|
2585 |
+ |
|
2586 |
+ request: function(messages) { |
|
2587 |
+ var href = this.endpoint.href, |
|
2588 |
+ xhr = Faye.ENV.ActiveXObject ? new ActiveXObject('Microsoft.XMLHTTP') : new XMLHttpRequest(), |
|
2589 |
+ self = this; |
|
2590 |
+ |
|
2591 |
+ xhr.open('POST', href, true); |
|
2592 |
+ xhr.setRequestHeader('Content-Type', 'application/json'); |
|
2593 |
+ xhr.setRequestHeader('Pragma', 'no-cache'); |
|
2594 |
+ xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest'); |
|
2595 |
+ |
|
2596 |
+ var headers = this._dispatcher.headers; |
|
2597 |
+ for (var key in headers) { |
|
2598 |
+ if (!headers.hasOwnProperty(key)) continue; |
|
2599 |
+ xhr.setRequestHeader(key, headers[key]); |
|
2600 |
+ } |
|
2601 |
+ |
|
2602 |
+ var abort = function() { xhr.abort() }; |
|
2603 |
+ if (Faye.ENV.onbeforeunload !== undefined) Faye.Event.on(Faye.ENV, 'beforeunload', abort); |
|
2604 |
+ |
|
2605 |
+ xhr.onreadystatechange = function() { |
|
2606 |
+ if (!xhr || xhr.readyState !== 4) return; |
|
2607 |
+ |
|
2608 |
+ var replies = null, |
|
2609 |
+ status = xhr.status, |
|
2610 |
+ text = xhr.responseText, |
|
2611 |
+ successful = (status >= 200 && status < 300) || status === 304 || status === 1223; |
|
2612 |
+ |
|
2613 |
+ if (Faye.ENV.onbeforeunload !== undefined) Faye.Event.detach(Faye.ENV, 'beforeunload', abort); |
|
2614 |
+ xhr.onreadystatechange = function() {}; |
|
2615 |
+ xhr = null; |
|
2616 |
+ |
|
2617 |
+ if (!successful) return self._handleError(messages); |
|
2618 |
+ |
|
2619 |
+ try { |
|
2620 |
+ replies = JSON.parse(text); |
|
2621 |
+ } catch (e) {} |
|
2622 |
+ |
|
2623 |
+ if (replies) |
|
2624 |
+ self._receive(replies); |
|
2625 |
+ else |
|
2626 |
+ self._handleError(messages); |
|
2627 |
+ }; |
|
2628 |
+ |
|
2629 |
+ xhr.send(this.encode(messages)); |
|
2630 |
+ return xhr; |
|
2631 |
+ } |
|
2632 |
+}), { |
|
2633 |
+ isUsable: function(dispatcher, endpoint, callback, context) { |
|
2634 |
+ callback.call(context, Faye.URI.isSameOrigin(endpoint)); |
|
2635 |
+ } |
|
2636 |
+}); |
|
2637 |
+ |
|
2638 |
+Faye.Transport.register('long-polling', Faye.Transport.XHR); |
|
2639 |
+ |
|
2640 |
+Faye.Transport.CORS = Faye.extend(Faye.Class(Faye.Transport, { |
|
2641 |
+ encode: function(messages) { |
|
2642 |
+ return 'message=' + encodeURIComponent(Faye.toJSON(messages)); |
|
2643 |
+ }, |
|
2644 |
+ |
|
2645 |
+ request: function(messages) { |
|
2646 |
+ var xhrClass = Faye.ENV.XDomainRequest ? XDomainRequest : XMLHttpRequest, |
|
2647 |
+ xhr = new xhrClass(), |
|
2648 |
+ headers = this._dispatcher.headers, |
|
2649 |
+ self = this, |
|
2650 |
+ key; |
|
2651 |
+ |
|
2652 |
+ xhr.open('POST', Faye.URI.stringify(this.endpoint), true); |
|
2653 |
+ |
|
2654 |
+ if (xhr.setRequestHeader) { |
|
2655 |
+ xhr.setRequestHeader('Pragma', 'no-cache'); |
|
2656 |
+ for (key in headers) { |
|
2657 |
+ if (!headers.hasOwnProperty(key)) continue; |
|
2658 |
+ xhr.setRequestHeader(key, headers[key]); |
|
2659 |
+ } |
|
2660 |
+ } |
|
2661 |
+ |
|
2662 |
+ var cleanUp = function() { |
|
2663 |
+ if (!xhr) return false; |
|
2664 |
+ xhr.onload = xhr.onerror = xhr.ontimeout = xhr.onprogress = null; |
|
2665 |
+ xhr = null; |
|
2666 |
+ }; |
|
2667 |
+ |
|
2668 |
+ xhr.onload = function() { |
|
2669 |
+ var replies = null; |
|
2670 |
+ try { |
|
2671 |
+ replies = JSON.parse(xhr.responseText); |
|
2672 |
+ } catch (e) {} |
|
2673 |
+ |
|
2674 |
+ cleanUp(); |
|
2675 |
+ |
|
2676 |
+ if (replies) |
|
2677 |
+ self._receive(replies); |
|
2678 |
+ else |
|
2679 |
+ self._handleError(messages); |
|
2680 |
+ }; |
|
2681 |
+ |
|
2682 |
+ xhr.onerror = xhr.ontimeout = function() { |
|
2683 |
+ cleanUp(); |
|
2684 |
+ self._handleError(messages); |
|
2685 |
+ }; |
|
2686 |
+ |
|
2687 |
+ xhr.onprogress = function() {}; |
|
2688 |
+ xhr.send(this.encode(messages)); |
|
2689 |
+ return xhr; |
|
2690 |
+ } |
|
2691 |
+}), { |
|
2692 |
+ isUsable: function(dispatcher, endpoint, callback, context) { |
|
2693 |
+ if (Faye.URI.isSameOrigin(endpoint)) |
|
2694 |
+ return callback.call(context, false); |
|
2695 |
+ |
|
2696 |
+ if (Faye.ENV.XDomainRequest) |
|
2697 |
+ return callback.call(context, endpoint.protocol === Faye.ENV.location.protocol); |
|
2698 |
+ |
|
2699 |
+ if (Faye.ENV.XMLHttpRequest) { |
|
2700 |
+ var xhr = new Faye.ENV.XMLHttpRequest(); |
|
2701 |
+ return callback.call(context, xhr.withCredentials !== undefined); |
|
2702 |
+ } |
|
2703 |
+ return callback.call(context, false); |
|
2704 |
+ } |
|
2705 |
+}); |
|
2706 |
+ |
|
2707 |
+Faye.Transport.register('cross-origin-long-polling', Faye.Transport.CORS); |
|
2708 |
+ |
|
2709 |
+Faye.Transport.JSONP = Faye.extend(Faye.Class(Faye.Transport, { |
|
2710 |
+ encode: function(messages) { |
|
2711 |
+ var url = Faye.copyObject(this.endpoint); |
|
2712 |
+ url.query.message = Faye.toJSON(messages); |
|
2713 |
+ url.query.jsonp = '__jsonp' + Faye.Transport.JSONP._cbCount + '__'; |
|
2714 |
+ return Faye.URI.stringify(url); |
|
2715 |
+ }, |
|
2716 |
+ |
|
2717 |
+ request: function(messages) { |
|
2718 |
+ var head = document.getElementsByTagName('head')[0], |
|
2719 |
+ script = document.createElement('script'), |
|
2720 |
+ callbackName = Faye.Transport.JSONP.getCallbackName(), |
|
2721 |
+ endpoint = Faye.copyObject(this.endpoint), |
|
2722 |
+ self = this; |
|
2723 |
+ |
|
2724 |
+ endpoint.query.message = Faye.toJSON(messages); |
|
2725 |
+ endpoint.query.jsonp = callbackName; |
|
2726 |
+ |
|
2727 |
+ var cleanup = function() { |
|
2728 |
+ if (!Faye.ENV[callbackName]) return false; |
|
2729 |
+ Faye.ENV[callbackName] = undefined; |
|
2730 |
+ try { delete Faye.ENV[callbackName] } catch (e) {} |
|
2731 |
+ script.parentNode.removeChild(script); |
|
2732 |
+ }; |
|
2733 |
+ |
|
2734 |
+ Faye.ENV[callbackName] = function(replies) { |
|
2735 |
+ cleanup(); |
|
2736 |
+ self._receive(replies); |
|
2737 |
+ }; |
|
2738 |
+ |
|
2739 |
+ script.type = 'text/javascript'; |
|
2740 |
+ script.src = Faye.URI.stringify(endpoint); |
|
2741 |
+ head.appendChild(script); |
|
2742 |
+ |
|
2743 |
+ script.onerror = function() { |
|
2744 |
+ cleanup(); |
|
2745 |
+ self._handleError(messages); |
|
2746 |
+ }; |
|
2747 |
+ |
|
2748 |
+ return {abort: cleanup}; |
|
2749 |
+ } |
|
2750 |
+}), { |
|
2751 |
+ _cbCount: 0, |
|
2752 |
+ |
|
2753 |
+ getCallbackName: function() { |
|
2754 |
+ this._cbCount += 1; |
|
2755 |
+ return '__jsonp' + this._cbCount + '__'; |
|
2756 |
+ }, |
|
2757 |
+ |
|
2758 |
+ isUsable: function(dispatcher, endpoint, callback, context) { |
|
2759 |
+ callback.call(context, true); |
|
2760 |
+ } |
|
2761 |
+}); |
|
2762 |
+ |
|
2763 |
+Faye.Transport.register('callback-polling', Faye.Transport.JSONP); |
|
2764 |
+ |
|
2765 |
+})(); |
@@ -0,0 +1,2194 @@ |
||
1 |
+'use strict'; |
|
2 |
+ |
|
3 |
+var Faye = { |
|
4 |
+ VERSION: '0.8.9', |
|
5 |
+ |
|
6 |
+ BAYEUX_VERSION: '1.0', |
|
7 |
+ ID_LENGTH: 160, |
|
8 |
+ JSONP_CALLBACK: 'jsonpcallback', |
|
9 |
+ CONNECTION_TYPES: ['long-polling', 'cross-origin-long-polling', 'callback-polling', 'websocket', 'eventsource', 'in-process'], |
|
10 |
+ |
|
11 |
+ MANDATORY_CONNECTION_TYPES: ['long-polling', 'callback-polling', 'in-process'], |
|
12 |
+ |
|
13 |
+ ENV: (typeof global === 'undefined') ? window : global, |
|
14 |
+ |
|
15 |
+ extend: function(dest, source, overwrite) { |
|
16 |
+ if (!source) return dest; |
|
17 |
+ for (var key in source) { |
|
18 |
+ if (!source.hasOwnProperty(key)) continue; |
|
19 |
+ if (dest.hasOwnProperty(key) && overwrite === false) continue; |
|
20 |
+ if (dest[key] !== source[key]) |
|
21 |
+ dest[key] = source[key]; |
|
22 |
+ } |
|
23 |
+ return dest; |
|
24 |
+ }, |
|
25 |
+ |
|
26 |
+ random: function(bitlength) { |
|
27 |
+ bitlength = bitlength || this.ID_LENGTH; |
|
28 |
+ if (bitlength > 32) { |
|
29 |
+ var parts = Math.ceil(bitlength / 32), |
|
30 |
+ string = ''; |
|
31 |
+ while (parts--) string += this.random(32); |
|
32 |
+ var chars = string.split(''), result = ''; |
|
33 |
+ while (chars.length > 0) result += chars.pop(); |
|
34 |
+ return result; |
|
35 |
+ } |
|
36 |
+ var limit = Math.pow(2, bitlength) - 1, |
|
37 |
+ maxSize = limit.toString(36).length, |
|
38 |
+ string = Math.floor(Math.random() * limit).toString(36); |
|
39 |
+ |
|
40 |
+ while (string.length < maxSize) string = '0' + string; |
|
41 |
+ return string; |
|
42 |
+ }, |
|
43 |
+ |
|
44 |
+ clientIdFromMessages: function(messages) { |
|
45 |
+ var first = [].concat(messages)[0]; |
|
46 |
+ return first && first.clientId; |
|
47 |
+ }, |
|
48 |
+ |
|
49 |
+ copyObject: function(object) { |
|
50 |
+ var clone, i, key; |
|
51 |
+ if (object instanceof Array) { |
|
52 |
+ clone = []; |
|
53 |
+ i = object.length; |
|
54 |
+ while (i--) clone[i] = Faye.copyObject(object[i]); |
|
55 |
+ return clone; |
|
56 |
+ } else if (typeof object === 'object') { |
|
57 |
+ clone = (object === null) ? null : {}; |
|
58 |
+ for (key in object) clone[key] = Faye.copyObject(object[key]); |
|
59 |
+ return clone; |
|
60 |
+ } else { |
|
61 |
+ return object; |
|
62 |
+ } |
|
63 |
+ }, |
|
64 |
+ |
|
65 |
+ commonElement: function(lista, listb) { |
|
66 |
+ for (var i = 0, n = lista.length; i < n; i++) { |
|
67 |
+ if (this.indexOf(listb, lista[i]) !== -1) |
|
68 |
+ return lista[i]; |
|
69 |
+ } |
|
70 |
+ return null; |
|
71 |
+ }, |
|
72 |
+ |
|
73 |
+ indexOf: function(list, needle) { |
|
74 |
+ if (list.indexOf) return list.indexOf(needle); |
|
75 |
+ |
|
76 |
+ for (var i = 0, n = list.length; i < n; i++) { |
|
77 |
+ if (list[i] === needle) return i; |
|
78 |
+ } |
|
79 |
+ return -1; |
|
80 |
+ }, |
|
81 |
+ |
|
82 |
+ map: function(object, callback, context) { |
|
83 |
+ if (object.map) return object.map(callback, context); |
|
84 |
+ var result = []; |
|
85 |
+ |
|
86 |
+ if (object instanceof Array) { |
|
87 |
+ for (var i = 0, n = object.length; i < n; i++) { |
|
88 |
+ result.push(callback.call(context || null, object[i], i)); |
|
89 |
+ } |
|
90 |
+ } else { |
|
91 |
+ for (var key in object) { |
|
92 |
+ if (!object.hasOwnProperty(key)) continue; |
|
93 |
+ result.push(callback.call(context || null, key, object[key])); |
|
94 |
+ } |
|
95 |
+ } |
|
96 |
+ return result; |
|
97 |
+ }, |
|
98 |
+ |
|
99 |
+ filter: function(array, callback, context) { |
|
100 |
+ var result = []; |
|
101 |
+ for (var i = 0, n = array.length; i < n; i++) { |
|
102 |
+ if (callback.call(context || null, array[i], i)) |
|
103 |
+ result.push(array[i]); |
|
104 |
+ } |
|
105 |
+ return result; |
|
106 |
+ }, |
|
107 |
+ |
|
108 |
+ asyncEach: function(list, iterator, callback, context) { |
|
109 |
+ var n = list.length, |
|
110 |
+ i = -1, |
|
111 |
+ calls = 0, |
|
112 |
+ looping = false; |
|
113 |
+ |
|
114 |
+ var iterate = function() { |
|
115 |
+ calls -= 1; |
|
116 |
+ i += 1; |
|
117 |
+ if (i === n) return callback && callback.call(context); |
|
118 |
+ iterator(list[i], resume); |
|
119 |
+ }; |
|
120 |
+ |
|
121 |
+ var loop = function() { |
|
122 |
+ if (looping) return; |
|
123 |
+ looping = true; |
|
124 |
+ while (calls > 0) iterate(); |
|
125 |
+ looping = false; |
|
126 |
+ }; |
|
127 |
+ |
|
128 |
+ var resume = function() { |
|
129 |
+ calls += 1; |
|
130 |
+ loop(); |
|
131 |
+ }; |
|
132 |
+ resume(); |
|
133 |
+ }, |
|
134 |
+ |
|
135 |
+ // http://assanka.net/content/tech/2009/09/02/json2-js-vs-prototype/ |
|
136 |
+ toJSON: function(object) { |
|
137 |
+ if (this.stringify) |
|
138 |
+ return this.stringify(object, function(key, value) { |
|
139 |
+ return (this[key] instanceof Array) |
|
140 |
+ ? this[key] |
|
141 |
+ : value; |
|
142 |
+ }); |
|
143 |
+ |
|
144 |
+ return JSON.stringify(object); |
|
145 |
+ }, |
|
146 |
+ |
|
147 |
+ logger: function(message) { |
|
148 |
+ if (typeof console !== 'undefined') console.log(message); |
|
149 |
+ }, |
|
150 |
+ |
|
151 |
+ timestamp: function() { |
|
152 |
+ var date = new Date(), |
|
153 |
+ year = date.getFullYear(), |
|
154 |
+ month = date.getMonth() + 1, |
|
155 |
+ day = date.getDate(), |
|
156 |
+ hour = date.getHours(), |
|
157 |
+ minute = date.getMinutes(), |
|
158 |
+ second = date.getSeconds(); |
|
159 |
+ |
|
160 |
+ var pad = function(n) { |
|
161 |
+ return n < 10 ? '0' + n : String(n); |
|
162 |
+ }; |
|
163 |
+ |
|
164 |
+ return pad(year) + '-' + pad(month) + '-' + pad(day) + ' ' + |
|
165 |
+ pad(hour) + ':' + pad(minute) + ':' + pad(second); |
|
166 |
+ } |
|
167 |
+}; |
|
168 |
+ |
|
169 |
+if (typeof window !== 'undefined') |
|
170 |
+ window.Faye = Faye; |
|
171 |
+ |
|
172 |
+ |
|
173 |
+Faye.Class = function(parent, methods) { |
|
174 |
+ if (typeof parent !== 'function') { |
|
175 |
+ methods = parent; |
|
176 |
+ parent = Object; |
|
177 |
+ } |
|
178 |
+ |
|
179 |
+ var klass = function() { |
|
180 |
+ if (!this.initialize) return this; |
|
181 |
+ return this.initialize.apply(this, arguments) || this; |
|
182 |
+ }; |
|
183 |
+ |
|
184 |
+ var bridge = function() {}; |
|
185 |
+ bridge.prototype = parent.prototype; |
|
186 |
+ |
|
187 |
+ klass.prototype = new bridge(); |
|
188 |
+ Faye.extend(klass.prototype, methods); |
|
189 |
+ |
|
190 |
+ return klass; |
|
191 |
+}; |
|
192 |
+ |
|
193 |
+ |
|
194 |
+Faye.Namespace = Faye.Class({ |
|
195 |
+ initialize: function() { |
|
196 |
+ this._used = {}; |
|
197 |
+ }, |
|
198 |
+ |
|
199 |
+ exists: function(id) { |
|
200 |
+ return this._used.hasOwnProperty(id); |
|
201 |
+ }, |
|
202 |
+ |
|
203 |
+ generate: function() { |
|
204 |
+ var name = Faye.random(); |
|
205 |
+ while (this._used.hasOwnProperty(name)) |
|
206 |
+ name = Faye.random(); |
|
207 |
+ return this._used[name] = name; |
|
208 |
+ }, |
|
209 |
+ |
|
210 |
+ release: function(id) { |
|
211 |
+ delete this._used[id]; |
|
212 |
+ } |
|
213 |
+}); |
|
214 |
+ |
|
215 |
+ |
|
216 |
+Faye.Error = Faye.Class({ |
|
217 |
+ initialize: function(code, params, message) { |
|
218 |
+ this.code = code; |
|
219 |
+ this.params = Array.prototype.slice.call(params); |
|
220 |
+ this.message = message; |
|
221 |
+ }, |
|
222 |
+ |
|
223 |
+ toString: function() { |
|
224 |
+ return this.code + ':' + |
|
225 |
+ this.params.join(',') + ':' + |
|
226 |
+ this.message; |
|
227 |
+ } |
|
228 |
+}); |
|
229 |
+ |
|
230 |
+Faye.Error.parse = function(message) { |
|
231 |
+ message = message || ''; |
|
232 |
+ if (!Faye.Grammar.ERROR.test(message)) return new this(null, [], message); |
|
233 |
+ |
|
234 |
+ var parts = message.split(':'), |
|
235 |
+ code = parseInt(parts[0]), |
|
236 |
+ params = parts[1].split(','), |
|
237 |
+ message = parts[2]; |
|
238 |
+ |
|
239 |
+ return new this(code, params, message); |
|
240 |
+}; |
|
241 |
+ |
|
242 |
+ |
|
243 |
+Faye.Error.versionMismatch = function() { |
|
244 |
+ return new this(300, arguments, "Version mismatch").toString(); |
|
245 |
+}; |
|
246 |
+ |
|
247 |
+Faye.Error.conntypeMismatch = function() { |
|
248 |
+ return new this(301, arguments, "Connection types not supported").toString(); |
|
249 |
+}; |
|
250 |
+ |
|
251 |
+Faye.Error.extMismatch = function() { |
|
252 |
+ return new this(302, arguments, "Extension mismatch").toString(); |
|
253 |
+}; |
|
254 |
+ |
|
255 |
+Faye.Error.badRequest = function() { |
|
256 |
+ return new this(400, arguments, "Bad request").toString(); |
|
257 |
+}; |
|
258 |
+ |
|
259 |
+Faye.Error.clientUnknown = function() { |
|
260 |
+ return new this(401, arguments, "Unknown client").toString(); |
|
261 |
+}; |
|
262 |
+ |
|
263 |
+Faye.Error.parameterMissing = function() { |
|
264 |
+ return new this(402, arguments, "Missing required parameter").toString(); |
|
265 |
+}; |
|
266 |
+ |
|
267 |
+Faye.Error.channelForbidden = function() { |
|
268 |
+ return new this(403, arguments, "Forbidden channel").toString(); |
|
269 |
+}; |
|
270 |
+ |
|
271 |
+Faye.Error.channelUnknown = function() { |
|
272 |
+ return new this(404, arguments, "Unknown channel").toString(); |
|
273 |
+}; |
|
274 |
+ |
|
275 |
+Faye.Error.channelInvalid = function() { |
|
276 |
+ return new this(405, arguments, "Invalid channel").toString(); |
|
277 |
+}; |
|
278 |
+ |
|
279 |
+Faye.Error.extUnknown = function() { |
|
280 |
+ return new this(406, arguments, "Unknown extension").toString(); |
|
281 |
+}; |
|
282 |
+ |
|
283 |
+Faye.Error.publishFailed = function() { |
|
284 |
+ return new this(407, arguments, "Failed to publish").toString(); |
|
285 |
+}; |
|
286 |
+ |
|
287 |
+Faye.Error.serverError = function() { |
|
288 |
+ return new this(500, arguments, "Internal server error").toString(); |
|
289 |
+}; |
|
290 |
+ |
|
291 |
+ |
|
292 |
+ |
|
293 |
+Faye.Deferrable = { |
|
294 |
+ callback: function(callback, context) { |
|
295 |
+ if (!callback) return; |
|
296 |
+ |
|
297 |
+ if (this._deferredStatus === 'succeeded') |
|
298 |
+ return callback.apply(context, this._deferredArgs); |
|
299 |
+ |
|
300 |
+ this._callbacks = this._callbacks || []; |
|
301 |
+ this._callbacks.push([callback, context]); |
|
302 |
+ }, |
|
303 |
+ |
|
304 |
+ timeout: function(seconds, message) { |
|
305 |
+ var _this = this; |
|
306 |
+ var timer = Faye.ENV.setTimeout(function() { |
|
307 |
+ _this.setDeferredStatus('failed', message); |
|
308 |
+ }, seconds * 1000); |
|
309 |
+ this._timer = timer; |
|
310 |
+ }, |
|
311 |
+ |
|
312 |
+ errback: function(callback, context) { |
|
313 |
+ if (!callback) return; |
|
314 |
+ |
|
315 |
+ if (this._deferredStatus === 'failed') |
|
316 |
+ return callback.apply(context, this._deferredArgs); |
|
317 |
+ |
|
318 |
+ this._errbacks = this._errbacks || []; |
|
319 |
+ this._errbacks.push([callback, context]); |
|
320 |
+ }, |
|
321 |
+ |
|
322 |
+ setDeferredStatus: function() { |
|
323 |
+ if (this._timer) |
|
324 |
+ Faye.ENV.clearTimeout(this._timer); |
|
325 |
+ |
|
326 |
+ var args = Array.prototype.slice.call(arguments), |
|
327 |
+ status = args.shift(), |
|
328 |
+ callbacks; |
|
329 |
+ |
|
330 |
+ this._deferredStatus = status; |
|
331 |
+ this._deferredArgs = args; |
|
332 |
+ |
|
333 |
+ if (status === 'succeeded') |
|
334 |
+ callbacks = this._callbacks; |
|
335 |
+ else if (status === 'failed') |
|
336 |
+ callbacks = this._errbacks; |
|
337 |
+ |
|
338 |
+ if (!callbacks) return; |
|
339 |
+ |
|
340 |
+ var callback; |
|
341 |
+ while (callback = callbacks.shift()) |
|
342 |
+ callback[0].apply(callback[1], this._deferredArgs); |
|
343 |
+ } |
|
344 |
+}; |
|
345 |
+ |
|
346 |
+ |
|
347 |
+Faye.Publisher = { |
|
348 |
+ countListeners: function(eventType) { |
|
349 |
+ if (!this._subscribers || !this._subscribers[eventType]) return 0; |
|
350 |
+ return this._subscribers[eventType].length; |
|
351 |
+ }, |
|
352 |
+ |
|
353 |
+ bind: function(eventType, listener, context) { |
|
354 |
+ this._subscribers = this._subscribers || {}; |
|
355 |
+ var list = this._subscribers[eventType] = this._subscribers[eventType] || []; |
|
356 |
+ list.push([listener, context]); |
|
357 |
+ }, |
|
358 |
+ |
|
359 |
+ unbind: function(eventType, listener, context) { |
|
360 |
+ if (!this._subscribers || !this._subscribers[eventType]) return; |
|
361 |
+ |
|
362 |
+ if (!listener) { |
|
363 |
+ delete this._subscribers[eventType]; |
|
364 |
+ return; |
|
365 |
+ } |
|
366 |
+ var list = this._subscribers[eventType], |
|
367 |
+ i = list.length; |
|
368 |
+ |
|
369 |
+ while (i--) { |
|
370 |
+ if (listener !== list[i][0]) continue; |
|
371 |
+ if (context && list[i][1] !== context) continue; |
|
372 |
+ list.splice(i,1); |
|
373 |
+ } |
|
374 |
+ }, |
|
375 |
+ |
|
376 |
+ trigger: function() { |
|
377 |
+ var args = Array.prototype.slice.call(arguments), |
|
378 |
+ eventType = args.shift(); |
|
379 |
+ |
|
380 |
+ if (!this._subscribers || !this._subscribers[eventType]) return; |
|
381 |
+ |
|
382 |
+ var listeners = this._subscribers[eventType].slice(), |
|
383 |
+ listener; |
|
384 |
+ |
|
385 |
+ for (var i = 0, n = listeners.length; i < n; i++) { |
|
386 |
+ listener = listeners[i]; |
|
387 |
+ listener[0].apply(listener[1], args); |
|
388 |
+ } |
|
389 |
+ } |
|
390 |
+}; |
|
391 |
+ |
|
392 |
+ |
|
393 |
+Faye.Timeouts = { |
|
394 |
+ addTimeout: function(name, delay, callback, context) { |
|
395 |
+ this._timeouts = this._timeouts || {}; |
|
396 |
+ if (this._timeouts.hasOwnProperty(name)) return; |
|
397 |
+ var self = this; |
|
398 |
+ this._timeouts[name] = Faye.ENV.setTimeout(function() { |
|
399 |
+ delete self._timeouts[name]; |
|
400 |
+ callback.call(context); |
|
401 |
+ }, 1000 * delay); |
|
402 |
+ }, |
|
403 |
+ |
|
404 |
+ removeTimeout: function(name) { |
|
405 |
+ this._timeouts = this._timeouts || {}; |
|
406 |
+ var timeout = this._timeouts[name]; |
|
407 |
+ if (!timeout) return; |
|
408 |
+ clearTimeout(timeout); |
|
409 |
+ delete this._timeouts[name]; |
|
410 |
+ } |
|
411 |
+}; |
|
412 |
+ |
|
413 |
+ |
|
414 |
+Faye.Logging = { |
|
415 |
+ LOG_LEVELS: { |
|
416 |
+ error: 3, |
|
417 |
+ warn: 2, |
|
418 |
+ info: 1, |
|
419 |
+ debug: 0 |
|
420 |
+ }, |
|
421 |
+ |
|
422 |
+ logLevel: 'error', |
|
423 |
+ |
|
424 |
+ log: function(messageArgs, level) { |
|
425 |
+ if (!Faye.logger) return; |
|
426 |
+ |
|
427 |
+ var levels = Faye.Logging.LOG_LEVELS; |
|
428 |
+ if (levels[Faye.Logging.logLevel] > levels[level]) return; |
|
429 |
+ |
|
430 |
+ var messageArgs = Array.prototype.slice.apply(messageArgs), |
|
431 |
+ banner = ' [' + level.toUpperCase() + '] [Faye', |
|
432 |
+ klass = this.className, |
|
433 |
+ |
|
434 |
+ message = messageArgs.shift().replace(/\?/g, function() { |
|
435 |
+ try { |
|
436 |
+ return Faye.toJSON(messageArgs.shift()); |
|
437 |
+ } catch (e) { |
|
438 |
+ return '[Object]'; |
|
439 |
+ } |
|
440 |
+ }); |
|
441 |
+ |
|
442 |
+ for (var key in Faye) { |
|
443 |
+ if (klass) continue; |
|
444 |
+ if (typeof Faye[key] !== 'function') continue; |
|
445 |
+ if (this instanceof Faye[key]) klass = key; |
|
446 |
+ } |
|
447 |
+ if (klass) banner += '.' + klass; |
|
448 |
+ banner += '] '; |
|
449 |
+ |
|
450 |
+ Faye.logger(Faye.timestamp() + banner + message); |
|
451 |
+ } |
|
452 |
+}; |
|
453 |
+ |
|
454 |
+(function() { |
|
455 |
+ for (var key in Faye.Logging.LOG_LEVELS) |
|
456 |
+ (function(level, value) { |
|
457 |
+ Faye.Logging[level] = function() { |
|
458 |
+ this.log(arguments, level); |
|
459 |
+ }; |
|
460 |
+ })(key, Faye.Logging.LOG_LEVELS[key]); |
|
461 |
+})(); |
|
462 |
+ |
|
463 |
+ |
|
464 |
+Faye.Grammar = { |
|
465 |
+ |
|
466 |
+ LOWALPHA: /^[a-z]$/, |
|
467 |
+ |
|
468 |
+ UPALPHA: /^[A-Z]$/, |
|
469 |
+ |
|
470 |
+ ALPHA: /^([a-z]|[A-Z])$/, |
|
471 |
+ |
|
472 |
+ DIGIT: /^[0-9]$/, |
|
473 |
+ |
|
474 |
+ ALPHANUM: /^(([a-z]|[A-Z])|[0-9])$/, |
|
475 |
+ |
|
476 |
+ MARK: /^(\-|\_|\!|\~|\(|\)|\$|\@)$/, |
|
477 |
+ |
|
478 |
+ STRING: /^(((([a-z]|[A-Z])|[0-9])|(\-|\_|\!|\~|\(|\)|\$|\@)| |\/|\*|\.))*$/, |
|
479 |
+ |
|
480 |
+ TOKEN: /^(((([a-z]|[A-Z])|[0-9])|(\-|\_|\!|\~|\(|\)|\$|\@)))+$/, |
|
481 |
+ |
|
482 |
+ INTEGER: /^([0-9])+$/, |
|
483 |
+ |
|
484 |
+ CHANNEL_SEGMENT: /^(((([a-z]|[A-Z])|[0-9])|(\-|\_|\!|\~|\(|\)|\$|\@)))+$/, |
|
485 |
+ |
|
486 |
+ CHANNEL_SEGMENTS: /^(((([a-z]|[A-Z])|[0-9])|(\-|\_|\!|\~|\(|\)|\$|\@)))+(\/(((([a-z]|[A-Z])|[0-9])|(\-|\_|\!|\~|\(|\)|\$|\@)))+)*$/, |
|
487 |
+ |
|
488 |
+ CHANNEL_NAME: /^\/(((([a-z]|[A-Z])|[0-9])|(\-|\_|\!|\~|\(|\)|\$|\@)))+(\/(((([a-z]|[A-Z])|[0-9])|(\-|\_|\!|\~|\(|\)|\$|\@)))+)*$/, |
|
489 |
+ |
|
490 |
+ WILD_CARD: /^\*{1,2}$/, |
|
491 |
+ |
|
492 |
+ CHANNEL_PATTERN: /^(\/(((([a-z]|[A-Z])|[0-9])|(\-|\_|\!|\~|\(|\)|\$|\@)))+)*\/\*{1,2}$/, |
|
493 |
+ |
|
494 |
+ VERSION_ELEMENT: /^(([a-z]|[A-Z])|[0-9])(((([a-z]|[A-Z])|[0-9])|\-|\_))*$/, |
|
495 |
+ |
|
496 |
+ VERSION: /^([0-9])+(\.(([a-z]|[A-Z])|[0-9])(((([a-z]|[A-Z])|[0-9])|\-|\_))*)*$/, |
|
497 |
+ |
|
498 |
+ CLIENT_ID: /^((([a-z]|[A-Z])|[0-9]))+$/, |
|
499 |
+ |
|
500 |
+ ID: /^((([a-z]|[A-Z])|[0-9]))+$/, |
|
501 |
+ |
|
502 |
+ ERROR_MESSAGE: /^(((([a-z]|[A-Z])|[0-9])|(\-|\_|\!|\~|\(|\)|\$|\@)| |\/|\*|\.))*$/, |
|
503 |
+ |
|
504 |
+ ERROR_ARGS: /^(((([a-z]|[A-Z])|[0-9])|(\-|\_|\!|\~|\(|\)|\$|\@)| |\/|\*|\.))*(,(((([a-z]|[A-Z])|[0-9])|(\-|\_|\!|\~|\(|\)|\$|\@)| |\/|\*|\.))*)*$/, |
|
505 |
+ |
|
506 |
+ ERROR_CODE: /^[0-9][0-9][0-9]$/, |
|
507 |
+ |
|
508 |
+ ERROR: /^([0-9][0-9][0-9]:(((([a-z]|[A-Z])|[0-9])|(\-|\_|\!|\~|\(|\)|\$|\@)| |\/|\*|\.))*(,(((([a-z]|[A-Z])|[0-9])|(\-|\_|\!|\~|\(|\)|\$|\@)| |\/|\*|\.))*)*:(((([a-z]|[A-Z])|[0-9])|(\-|\_|\!|\~|\(|\)|\$|\@)| |\/|\*|\.))*|[0-9][0-9][0-9]::(((([a-z]|[A-Z])|[0-9])|(\-|\_|\!|\~|\(|\)|\$|\@)| |\/|\*|\.))*)$/ |
|
509 |
+ |
|
510 |
+}; |
|
511 |
+ |
|
512 |
+ |
|
513 |
+Faye.Extensible = { |
|
514 |
+ addExtension: function(extension) { |
|
515 |
+ this._extensions = this._extensions || []; |
|
516 |
+ this._extensions.push(extension); |
|
517 |
+ if (extension.added) extension.added(this); |
|
518 |
+ }, |
|
519 |
+ |
|
520 |
+ removeExtension: function(extension) { |
|
521 |
+ if (!this._extensions) return; |
|
522 |
+ var i = this._extensions.length; |
|
523 |
+ while (i--) { |
|
524 |
+ if (this._extensions[i] !== extension) continue; |
|
525 |
+ this._extensions.splice(i,1); |
|
526 |
+ if (extension.removed) extension.removed(this); |
|
527 |
+ } |
|
528 |
+ }, |
|
529 |
+ |
|
530 |
+ pipeThroughExtensions: function(stage, message, callback, context) { |
|
531 |
+ this.debug('Passing through ? extensions: ?', stage, message); |
|
532 |
+ |
|
533 |
+ if (!this._extensions) return callback.call(context, message); |
|
534 |
+ var extensions = this._extensions.slice(); |
|
535 |
+ |
|
536 |
+ var pipe = function(message) { |
|
537 |
+ if (!message) return callback.call(context, message); |
|
538 |
+ |
|
539 |
+ var extension = extensions.shift(); |
|
540 |
+ if (!extension) return callback.call(context, message); |
|
541 |
+ |
|
542 |
+ if (extension[stage]) extension[stage](message, pipe); |
|
543 |
+ else pipe(message); |
|
544 |
+ }; |
|
545 |
+ pipe(message); |
|
546 |
+ } |
|
547 |
+}; |
|
548 |
+ |
|
549 |
+Faye.extend(Faye.Extensible, Faye.Logging); |
|
550 |
+ |
|
551 |
+Faye.Channel = Faye.Class({ |
|
552 |
+ initialize: function(name) { |
|
553 |
+ this.id = this.name = name; |
|
554 |
+ }, |
|
555 |
+ |
|
556 |
+ push: function(message) { |
|
557 |
+ this.trigger('message', message); |
|
558 |
+ }, |
|
559 |
+ |
|
560 |
+ isUnused: function() { |
|
561 |
+ return this.countListeners('message') === 0; |
|
562 |
+ } |
|
563 |
+}); |
|
564 |
+ |
|
565 |
+Faye.extend(Faye.Channel.prototype, Faye.Publisher); |
|
566 |
+ |
|
567 |
+Faye.extend(Faye.Channel, { |
|
568 |
+ HANDSHAKE: '/meta/handshake', |
|
569 |
+ CONNECT: '/meta/connect', |
|
570 |
+ SUBSCRIBE: '/meta/subscribe', |
|
571 |
+ UNSUBSCRIBE: '/meta/unsubscribe', |
|
572 |
+ DISCONNECT: '/meta/disconnect', |
|
573 |
+ |
|
574 |
+ META: 'meta', |
|
575 |
+ SERVICE: 'service', |
|
576 |
+ |
|
577 |
+ expand: function(name) { |
|
578 |
+ var segments = this.parse(name), |
|
579 |
+ channels = ['/**', name]; |
|
580 |
+ |
|
581 |
+ var copy = segments.slice(); |
|
582 |
+ copy[copy.length - 1] = '*'; |
|
583 |
+ channels.push(this.unparse(copy)); |
|
584 |
+ |
|
585 |
+ for (var i = 1, n = segments.length; i < n; i++) { |
|
586 |
+ copy = segments.slice(0, i); |
|
587 |
+ copy.push('**'); |
|
588 |
+ channels.push(this.unparse(copy)); |
|
589 |
+ } |
|
590 |
+ |
|
591 |
+ return channels; |
|
592 |
+ }, |
|
593 |
+ |
|
594 |
+ isValid: function(name) { |
|
595 |
+ return Faye.Grammar.CHANNEL_NAME.test(name) || |
|
596 |
+ Faye.Grammar.CHANNEL_PATTERN.test(name); |
|
597 |
+ }, |
|
598 |
+ |
|
599 |
+ parse: function(name) { |
|
600 |
+ if (!this.isValid(name)) return null; |
|
601 |
+ return name.split('/').slice(1); |
|
602 |
+ }, |
|
603 |
+ |
|
604 |
+ unparse: function(segments) { |
|
605 |
+ return '/' + segments.join('/'); |
|
606 |
+ }, |
|
607 |
+ |
|
608 |
+ isMeta: function(name) { |
|
609 |
+ var segments = this.parse(name); |
|
610 |
+ return segments ? (segments[0] === this.META) : null; |
|
611 |
+ }, |
|
612 |
+ |
|
613 |
+ isService: function(name) { |
|
614 |
+ var segments = this.parse(name); |
|
615 |
+ return segments ? (segments[0] === this.SERVICE) : null; |
|
616 |
+ }, |
|
617 |
+ |
|
618 |
+ isSubscribable: function(name) { |
|
619 |
+ if (!this.isValid(name)) return null; |
|
620 |
+ return !this.isMeta(name) && !this.isService(name); |
|
621 |
+ }, |
|
622 |
+ |
|
623 |
+ Set: Faye.Class({ |
|
624 |
+ initialize: function() { |
|
625 |
+ this._channels = {}; |
|
626 |
+ }, |
|
627 |
+ |
|
628 |
+ getKeys: function() { |
|
629 |
+ var keys = []; |
|
630 |
+ for (var key in this._channels) keys.push(key); |
|
631 |
+ return keys; |
|
632 |
+ }, |
|
633 |
+ |
|
634 |
+ remove: function(name) { |
|
635 |
+ delete this._channels[name]; |
|
636 |
+ }, |
|
637 |
+ |
|
638 |
+ hasSubscription: function(name) { |
|
639 |
+ return this._channels.hasOwnProperty(name); |
|
640 |
+ }, |
|
641 |
+ |
|
642 |
+ subscribe: function(names, callback, context) { |
|
643 |
+ if (!callback) return; |
|
644 |
+ var name; |
|
645 |
+ for (var i = 0, n = names.length; i < n; i++) { |
|
646 |
+ name = names[i]; |
|
647 |
+ var channel = this._channels[name] = this._channels[name] || new Faye.Channel(name); |
|
648 |
+ channel.bind('message', callback, context); |
|
649 |
+ } |
|
650 |
+ }, |
|
651 |
+ |
|
652 |
+ unsubscribe: function(name, callback, context) { |
|
653 |
+ var channel = this._channels[name]; |
|
654 |
+ if (!channel) return false; |
|
655 |
+ channel.unbind('message', callback, context); |
|
656 |
+ |
|
657 |
+ if (channel.isUnused()) { |
|
658 |
+ this.remove(name); |
|
659 |
+ return true; |
|
660 |
+ } else { |
|
661 |
+ return false; |
|
662 |
+ } |
|
663 |
+ }, |
|
664 |
+ |
|
665 |
+ distributeMessage: function(message) { |
|
666 |
+ var channels = Faye.Channel.expand(message.channel); |
|
667 |
+ |
|
668 |
+ for (var i = 0, n = channels.length; i < n; i++) { |
|
669 |
+ var channel = this._channels[channels[i]]; |
|
670 |
+ if (channel) channel.trigger('message', message.data); |
|
671 |
+ } |
|
672 |
+ } |
|
673 |
+ }) |
|
674 |
+}); |
|
675 |
+ |
|
676 |
+ |
|
677 |
+Faye.Publication = Faye.Class(Faye.Deferrable); |
|
678 |
+ |
|
679 |
+ |
|
680 |
+Faye.Subscription = Faye.Class({ |
|
681 |
+ initialize: function(client, channels, callback, context) { |
|
682 |
+ this._client = client; |
|
683 |
+ this._channels = channels; |
|
684 |
+ this._callback = callback; |
|
685 |
+ this._context = context; |
|
686 |
+ this._cancelled = false; |
|
687 |
+ }, |
|
688 |
+ |
|
689 |
+ cancel: function() { |
|
690 |
+ if (this._cancelled) return; |
|
691 |
+ this._client.unsubscribe(this._channels, this._callback, this._context); |
|
692 |
+ this._cancelled = true; |
|
693 |
+ }, |
|
694 |
+ |
|
695 |
+ unsubscribe: function() { |
|
696 |
+ this.cancel(); |
|
697 |
+ } |
|
698 |
+}); |
|
699 |
+ |
|
700 |
+Faye.extend(Faye.Subscription.prototype, Faye.Deferrable); |
|
701 |
+ |
|
702 |
+ |
|
703 |
+Faye.Client = Faye.Class({ |
|
704 |
+ UNCONNECTED: 1, |
|
705 |
+ CONNECTING: 2, |
|
706 |
+ CONNECTED: 3, |
|
707 |
+ DISCONNECTED: 4, |
|
708 |
+ |
|
709 |
+ HANDSHAKE: 'handshake', |
|
710 |
+ RETRY: 'retry', |
|
711 |
+ NONE: 'none', |
|
712 |
+ |
|
713 |
+ CONNECTION_TIMEOUT: 60.0, |
|
714 |
+ DEFAULT_RETRY: 5.0, |
|
715 |
+ |
|
716 |
+ DEFAULT_ENDPOINT: '/bayeux', |
|
717 |
+ INTERVAL: 0.0, |
|
718 |
+ |
|
719 |
+ initialize: function(endpoint, options) { |
|
720 |
+ this.info('New client created for ?', endpoint); |
|
721 |
+ |
|
722 |
+ this._options = options || {}; |
|
723 |
+ this.endpoint = endpoint || this.DEFAULT_ENDPOINT; |
|
724 |
+ this.endpoints = this._options.endpoints || {}; |
|
725 |
+ this.transports = {}; |
|
726 |
+ this._cookies = Faye.CookieJar && new Faye.CookieJar(); |
|
727 |
+ this._headers = {}; |
|
728 |
+ this._disabled = []; |
|
729 |
+ this.retry = this._options.retry || this.DEFAULT_RETRY; |
|
730 |
+ |
|
731 |
+ this._state = this.UNCONNECTED; |
|
732 |
+ this._channels = new Faye.Channel.Set(); |
|
733 |
+ this._messageId = 0; |
|
734 |
+ |
|
735 |
+ this._responseCallbacks = {}; |
|
736 |
+ |
|
737 |
+ this._advice = { |
|
738 |
+ reconnect: this.RETRY, |
|
739 |
+ interval: 1000 * (this._options.interval || this.INTERVAL), |
|
740 |
+ timeout: 1000 * (this._options.timeout || this.CONNECTION_TIMEOUT) |
|
741 |
+ }; |
|
742 |
+ |
|
743 |
+ if (Faye.Event) |
|
744 |
+ Faye.Event.on(Faye.ENV, 'beforeunload', function() { |
|
745 |
+ if (Faye.indexOf(this._disabled, 'autodisconnect') < 0) |
|
746 |
+ this.disconnect(); |
|
747 |
+ }, this); |
|
748 |
+ }, |
|
749 |
+ |
|
750 |
+ disable: function(feature) { |
|
751 |
+ this._disabled.push(feature); |
|
752 |
+ }, |
|
753 |
+ |
|
754 |
+ setHeader: function(name, value) { |
|
755 |
+ this._headers[name] = value; |
|
756 |
+ }, |
|
757 |
+ |
|
758 |
+ getClientId: function() { |
|
759 |
+ return this._clientId; |
|
760 |
+ }, |
|
761 |
+ |
|
762 |
+ getState: function() { |
|
763 |
+ switch (this._state) { |
|
764 |
+ case this.UNCONNECTED: return 'UNCONNECTED'; |
|
765 |
+ case this.CONNECTING: return 'CONNECTING'; |
|
766 |
+ case this.CONNECTED: return 'CONNECTED'; |
|
767 |
+ case this.DISCONNECTED: return 'DISCONNECTED'; |
|
768 |
+ } |
|
769 |
+ }, |
|
770 |
+ |
|
771 |
+ // Request |
|
772 |
+ // MUST include: * channel |
|
773 |
+ // * version |
|
774 |
+ // * supportedConnectionTypes |
|
775 |
+ // MAY include: * minimumVersion |
|
776 |
+ // * ext |
|
777 |
+ // * id |
|
778 |
+ // |
|
779 |
+ // Success Response Failed Response |
|
780 |
+ // MUST include: * channel MUST include: * channel |
|
781 |
+ // * version * successful |
|
782 |
+ // * supportedConnectionTypes * error |
|
783 |
+ // * clientId MAY include: * supportedConnectionTypes |
|
784 |
+ // * successful * advice |
|
785 |
+ // MAY include: * minimumVersion * version |
|
786 |
+ // * advice * minimumVersion |
|
787 |
+ // * ext * ext |
|
788 |
+ // * id * id |
|
789 |
+ // * authSuccessful |
|
790 |
+ handshake: function(callback, context) { |
|
791 |
+ if (this._advice.reconnect === this.NONE) return; |
|
792 |
+ if (this._state !== this.UNCONNECTED) return; |
|
793 |
+ |
|
794 |
+ this._state = this.CONNECTING; |
|
795 |
+ var self = this; |
|
796 |
+ |
|
797 |
+ this.info('Initiating handshake with ?', this.endpoint); |
|
798 |
+ this._selectTransport(Faye.MANDATORY_CONNECTION_TYPES); |
|
799 |
+ |
|
800 |
+ this._send({ |
|
801 |
+ channel: Faye.Channel.HANDSHAKE, |
|
802 |
+ version: Faye.BAYEUX_VERSION, |
|
803 |
+ supportedConnectionTypes: [this._transport.connectionType] |
|
804 |
+ |
|
805 |
+ }, function(response) { |
|
806 |
+ |
|
807 |
+ if (response.successful) { |
|
808 |
+ this._state = this.CONNECTED; |
|
809 |
+ this._clientId = response.clientId; |
|
810 |
+ |
|
811 |
+ this._selectTransport(response.supportedConnectionTypes); |
|
812 |
+ |
|
813 |
+ this.info('Handshake successful: ?', this._clientId); |
|
814 |
+ |
|
815 |
+ this.subscribe(this._channels.getKeys(), true); |
|
816 |
+ if (callback) callback.call(context); |
|
817 |
+ |
|
818 |
+ } else { |
|
819 |
+ this.info('Handshake unsuccessful'); |
|
820 |
+ Faye.ENV.setTimeout(function() { self.handshake(callback, context) }, this._advice.interval); |
|
821 |
+ this._state = this.UNCONNECTED; |
|
822 |
+ } |
|
823 |
+ }, this); |
|
824 |
+ }, |
|
825 |
+ |
|
826 |
+ // Request Response |
|
827 |
+ // MUST include: * channel MUST include: * channel |
|
828 |
+ // * clientId * successful |
|
829 |
+ // * connectionType * clientId |
|
830 |
+ // MAY include: * ext MAY include: * error |
|
831 |
+ // * id * advice |
|
832 |
+ // * ext |
|
833 |
+ // * id |
|
834 |
+ // * timestamp |
|
835 |
+ connect: function(callback, context) { |
|
836 |
+ if (this._advice.reconnect === this.NONE) return; |
|
837 |
+ if (this._state === this.DISCONNECTED) return; |
|
838 |
+ |
|
839 |
+ if (this._state === this.UNCONNECTED) |
|
840 |
+ return this.handshake(function() { this.connect(callback, context) }, this); |
|
841 |
+ |
|
842 |
+ this.callback(callback, context); |
|
843 |
+ if (this._state !== this.CONNECTED) return; |
|
844 |
+ |
|
845 |
+ this.info('Calling deferred actions for ?', this._clientId); |
|
846 |
+ this.setDeferredStatus('succeeded'); |
|
847 |
+ this.setDeferredStatus('deferred'); |
|
848 |
+ |
|
849 |
+ if (this._connectRequest) return; |
|
850 |
+ this._connectRequest = true; |
|
851 |
+ |
|
852 |
+ this.info('Initiating connection for ?', this._clientId); |
|
853 |
+ |
|
854 |
+ this._send({ |
|
855 |
+ channel: Faye.Channel.CONNECT, |
|
856 |
+ clientId: this._clientId, |
|
857 |
+ connectionType: this._transport.connectionType |
|
858 |
+ |
|
859 |
+ }, this._cycleConnection, this); |
|
860 |
+ }, |
|
861 |
+ |
|
862 |
+ // Request Response |
|
863 |
+ // MUST include: * channel MUST include: * channel |
|
864 |
+ // * clientId * successful |
|
865 |
+ // MAY include: * ext * clientId |
|
866 |
+ // * id MAY include: * error |
|
867 |
+ // * ext |
|
868 |
+ // * id |
|
869 |
+ disconnect: function() { |
|
870 |
+ if (this._state !== this.CONNECTED) return; |
|
871 |
+ this._state = this.DISCONNECTED; |
|
872 |
+ |
|
873 |
+ this.info('Disconnecting ?', this._clientId); |
|
874 |
+ |
|
875 |
+ this._send({ |
|
876 |
+ channel: Faye.Channel.DISCONNECT, |
|
877 |
+ clientId: this._clientId |
|
878 |
+ |
|
879 |
+ }, function(response) { |
|
880 |
+ if (response.successful) this._transport.close(); |
|
881 |
+ }, this); |
|
882 |
+ |
|
883 |
+ this.info('Clearing channel listeners for ?', this._clientId); |
|
884 |
+ this._channels = new Faye.Channel.Set(); |
|
885 |
+ }, |
|
886 |
+ |
|
887 |
+ // Request Response |
|
888 |
+ // MUST include: * channel MUST include: * channel |
|
889 |
+ // * clientId * successful |
|
890 |
+ // * subscription * clientId |
|
891 |
+ // MAY include: * ext * subscription |
|
892 |
+ // * id MAY include: * error |
|
893 |
+ // * advice |
|
894 |
+ // * ext |
|
895 |
+ // * id |
|
896 |
+ // * timestamp |
|
897 |
+ subscribe: function(channel, callback, context) { |
|
898 |
+ if (channel instanceof Array) |
|
899 |
+ return Faye.map(channel, function(c) { |
|
900 |
+ return this.subscribe(c, callback, context); |
|
901 |
+ }, this); |
|
902 |
+ |
|
903 |
+ var subscription = new Faye.Subscription(this, channel, callback, context), |
|
904 |
+ force = (callback === true), |
|
905 |
+ hasSubscribe = this._channels.hasSubscription(channel); |
|
906 |
+ |
|
907 |
+ if (hasSubscribe && !force) { |
|
908 |
+ this._channels.subscribe([channel], callback, context); |
|
909 |
+ subscription.setDeferredStatus('succeeded'); |
|
910 |
+ return subscription; |
|
911 |
+ } |
|
912 |
+ |
|
913 |
+ this.connect(function() { |
|
914 |
+ this.info('Client ? attempting to subscribe to ?', this._clientId, channel); |
|
915 |
+ if (!force) this._channels.subscribe([channel], callback, context); |
|
916 |
+ |
|
917 |
+ this._send({ |
|
918 |
+ channel: Faye.Channel.SUBSCRIBE, |
|
919 |
+ clientId: this._clientId, |
|
920 |
+ subscription: channel |
|
921 |
+ |
|
922 |
+ }, function(response) { |
|
923 |
+ if (!response.successful) { |
|
924 |
+ subscription.setDeferredStatus('failed', Faye.Error.parse(response.error)); |
|
925 |
+ return this._channels.unsubscribe(channel, callback, context); |
|
926 |
+ } |
|
927 |
+ |
|
928 |
+ var channels = [].concat(response.subscription); |
|
929 |
+ this.info('Subscription acknowledged for ? to ?', this._clientId, channels); |
|
930 |
+ subscription.setDeferredStatus('succeeded'); |
|
931 |
+ }, this); |
|
932 |
+ }, this); |
|
933 |
+ |
|
934 |
+ return subscription; |
|
935 |
+ }, |
|
936 |
+ |
|
937 |
+ // Request Response |
|
938 |
+ // MUST include: * channel MUST include: * channel |
|
939 |
+ // * clientId * successful |
|
940 |
+ // * subscription * clientId |
|
941 |
+ // MAY include: * ext * subscription |
|
942 |
+ // * id MAY include: * error |
|
943 |
+ // * advice |
|
944 |
+ // * ext |
|
945 |
+ // * id |
|
946 |
+ // * timestamp |
|
947 |
+ unsubscribe: function(channel, callback, context) { |
|
948 |
+ if (channel instanceof Array) |
|
949 |
+ return Faye.map(channel, function(c) { |
|
950 |
+ return this.unsubscribe(c, callback, context); |
|
951 |
+ }, this); |
|
952 |
+ |
|
953 |
+ var dead = this._channels.unsubscribe(channel, callback, context); |
|
954 |
+ if (!dead) return; |
|
955 |
+ |
|
956 |
+ this.connect(function() { |
|
957 |
+ this.info('Client ? attempting to unsubscribe from ?', this._clientId, channel); |
|
958 |
+ |
|
959 |
+ this._send({ |
|
960 |
+ channel: Faye.Channel.UNSUBSCRIBE, |
|
961 |
+ clientId: this._clientId, |
|
962 |
+ subscription: channel |
|
963 |
+ |
|
964 |
+ }, function(response) { |
|
965 |
+ if (!response.successful) return; |
|
966 |
+ |
|
967 |
+ var channels = [].concat(response.subscription); |
|
968 |
+ this.info('Unsubscription acknowledged for ? from ?', this._clientId, channels); |
|
969 |
+ }, this); |
|
970 |
+ }, this); |
|
971 |
+ }, |
|
972 |
+ |
|
973 |
+ // Request Response |
|
974 |
+ // MUST include: * channel MUST include: * channel |
|
975 |
+ // * data * successful |
|
976 |
+ // MAY include: * clientId MAY include: * id |
|
977 |
+ // * id * error |
|
978 |
+ // * ext * ext |
|
979 |
+ publish: function(channel, data) { |
|
980 |
+ var publication = new Faye.Publication(); |
|
981 |
+ |
|
982 |
+ this.connect(function() { |
|
983 |
+ this.info('Client ? queueing published message to ?: ?', this._clientId, channel, data); |
|
984 |
+ |
|
985 |
+ this._send({ |
|
986 |
+ channel: channel, |
|
987 |
+ data: data, |
|
988 |
+ clientId: this._clientId |
|
989 |
+ }, function(response) { |
|
990 |
+ if (response.successful) |
|
991 |
+ publication.setDeferredStatus('succeeded'); |
|
992 |
+ else |
|
993 |
+ publication.setDeferredStatus('failed', Faye.Error.parse(response.error)); |
|
994 |
+ }, this); |
|
995 |
+ }, this); |
|
996 |
+ |
|
997 |
+ return publication; |
|
998 |
+ }, |
|
999 |
+ |
|
1000 |
+ receiveMessage: function(message) { |
|
1001 |
+ this.pipeThroughExtensions('incoming', message, function(message) { |
|
1002 |
+ if (!message) return; |
|
1003 |
+ |
|
1004 |
+ if (message.advice) this._handleAdvice(message.advice); |
|
1005 |
+ this._deliverMessage(message); |
|
1006 |
+ |
|
1007 |
+ if (message.successful === undefined) return; |
|
1008 |
+ |
|
1009 |
+ var callback = this._responseCallbacks[message.id]; |
|
1010 |
+ if (!callback) return; |
|
1011 |
+ |
|
1012 |
+ delete this._responseCallbacks[message.id]; |
|
1013 |
+ callback[0].call(callback[1], message); |
|
1014 |
+ }, this); |
|
1015 |
+ }, |
|
1016 |
+ |
|
1017 |
+ _selectTransport: function(transportTypes) { |
|
1018 |
+ Faye.Transport.get(this, transportTypes, this._disabled, function(transport) { |
|
1019 |
+ this.debug('Selected ? transport for ?', transport.connectionType, transport.endpoint); |
|
1020 |
+ |
|
1021 |
+ if (transport === this._transport) return; |
|
1022 |
+ if (this._transport) this._transport.close(); |
|
1023 |
+ |
|
1024 |
+ this._transport = transport; |
|
1025 |
+ this._transport.cookies = this._cookies; |
|
1026 |
+ this._transport.headers = this._headers; |
|
1027 |
+ |
|
1028 |
+ transport.bind('down', function() { |
|
1029 |
+ if (this._transportUp !== undefined && !this._transportUp) return; |
|
1030 |
+ this._transportUp = false; |
|
1031 |
+ this.trigger('transport:down'); |
|
1032 |
+ }, this); |
|
1033 |
+ |
|
1034 |
+ transport.bind('up', function() { |
|
1035 |
+ if (this._transportUp !== undefined && this._transportUp) return; |
|
1036 |
+ this._transportUp = true; |
|
1037 |
+ this.trigger('transport:up'); |
|
1038 |
+ }, this); |
|
1039 |
+ }, this); |
|
1040 |
+ }, |
|
1041 |
+ |
|
1042 |
+ _send: function(message, callback, context) { |
|
1043 |
+ message.id = this._generateMessageId(); |
|
1044 |
+ if (callback) this._responseCallbacks[message.id] = [callback, context]; |
|
1045 |
+ |
|
1046 |
+ this.pipeThroughExtensions('outgoing', message, function(message) { |
|
1047 |
+ if (!message) return; |
|
1048 |
+ this._transport.send(message, this._advice.timeout / 1000); |
|
1049 |
+ }, this); |
|
1050 |
+ }, |
|
1051 |
+ |
|
1052 |
+ _generateMessageId: function() { |
|
1053 |
+ this._messageId += 1; |
|
1054 |
+ if (this._messageId >= Math.pow(2,32)) this._messageId = 0; |
|
1055 |
+ return this._messageId.toString(36); |
|
1056 |
+ }, |
|
1057 |
+ |
|
1058 |
+ _handleAdvice: function(advice) { |
|
1059 |
+ Faye.extend(this._advice, advice); |
|
1060 |
+ |
|
1061 |
+ if (this._advice.reconnect === this.HANDSHAKE && this._state !== this.DISCONNECTED) { |
|
1062 |
+ this._state = this.UNCONNECTED; |
|
1063 |
+ this._clientId = null; |
|
1064 |
+ this._cycleConnection(); |
|
1065 |
+ } |
|
1066 |
+ }, |
|
1067 |
+ |
|
1068 |
+ _deliverMessage: function(message) { |
|
1069 |
+ if (!message.channel || message.data === undefined) return; |
|
1070 |
+ this.info('Client ? calling listeners for ? with ?', this._clientId, message.channel, message.data); |
|
1071 |
+ this._channels.distributeMessage(message); |
|
1072 |
+ }, |
|
1073 |
+ |
|
1074 |
+ _teardownConnection: function() { |
|
1075 |
+ if (!this._connectRequest) return; |
|
1076 |
+ this._connectRequest = null; |
|
1077 |
+ this.info('Closed connection for ?', this._clientId); |
|
1078 |
+ }, |
|
1079 |
+ |
|
1080 |
+ _cycleConnection: function() { |
|
1081 |
+ this._teardownConnection(); |
|
1082 |
+ var self = this; |
|
1083 |
+ Faye.ENV.setTimeout(function() { self.connect() }, this._advice.interval); |
|
1084 |
+ } |
|
1085 |
+}); |
|
1086 |
+ |
|
1087 |
+Faye.extend(Faye.Client.prototype, Faye.Deferrable); |
|
1088 |
+Faye.extend(Faye.Client.prototype, Faye.Publisher); |
|
1089 |
+Faye.extend(Faye.Client.prototype, Faye.Logging); |
|
1090 |
+Faye.extend(Faye.Client.prototype, Faye.Extensible); |
|
1091 |
+ |
|
1092 |
+ |
|
1093 |
+Faye.Transport = Faye.extend(Faye.Class({ |
|
1094 |
+ MAX_DELAY: 0.0, |
|
1095 |
+ batching: true, |
|
1096 |
+ |
|
1097 |
+ initialize: function(client, endpoint) { |
|
1098 |
+ this._client = client; |
|
1099 |
+ this.endpoint = endpoint; |
|
1100 |
+ this._outbox = []; |
|
1101 |
+ }, |
|
1102 |
+ |
|
1103 |
+ close: function() {}, |
|
1104 |
+ |
|
1105 |
+ send: function(message, timeout) { |
|
1106 |
+ this.debug('Client ? sending message to ?: ?', |
|
1107 |
+ this._client._clientId, this.endpoint, message); |
|
1108 |
+ |
|
1109 |
+ if (!this.batching) return this.request([message], timeout); |
|
1110 |
+ |
|
1111 |
+ this._outbox.push(message); |
|
1112 |
+ this._timeout = timeout; |
|
1113 |
+ |
|
1114 |
+ if (message.channel === Faye.Channel.HANDSHAKE) |
|
1115 |
+ return this.addTimeout('publish', 0.01, this.flush, this); |
|
1116 |
+ |
|
1117 |
+ if (message.channel === Faye.Channel.CONNECT) |
|
1118 |
+ this._connectMessage = message; |
|
1119 |
+ |
|
1120 |
+ if (this.shouldFlush && this.shouldFlush(this._outbox)) |
|
1121 |
+ return this.flush(); |
|
1122 |
+ |
|
1123 |
+ this.addTimeout('publish', this.MAX_DELAY, this.flush, this); |
|
1124 |
+ }, |
|
1125 |
+ |
|
1126 |
+ flush: function() { |
|
1127 |
+ this.removeTimeout('publish'); |
|
1128 |
+ |
|
1129 |
+ if (this._outbox.length > 1 && this._connectMessage) |
|
1130 |
+ this._connectMessage.advice = {timeout: 0}; |
|
1131 |
+ |
|
1132 |
+ this.request(this._outbox, this._timeout); |
|
1133 |
+ |
|
1134 |
+ this._connectMessage = null; |
|
1135 |
+ this._outbox = []; |
|
1136 |
+ }, |
|
1137 |
+ |
|
1138 |
+ receive: function(responses) { |
|
1139 |
+ this.debug('Client ? received from ?: ?', |
|
1140 |
+ this._client._clientId, this.endpoint, responses); |
|
1141 |
+ |
|
1142 |
+ for (var i = 0, n = responses.length; i < n; i++) { |
|
1143 |
+ this._client.receiveMessage(responses[i]); |
|
1144 |
+ } |
|
1145 |
+ }, |
|
1146 |
+ |
|
1147 |
+ retry: function(message, timeout) { |
|
1148 |
+ var called = false, |
|
1149 |
+ retry = this._client.retry * 1000, |
|
1150 |
+ self = this; |
|
1151 |
+ |
|
1152 |
+ return function() { |
|
1153 |
+ if (called) return; |
|
1154 |
+ called = true; |
|
1155 |
+ Faye.ENV.setTimeout(function() { self.request(message, timeout) }, retry); |
|
1156 |
+ }; |
|
1157 |
+ } |
|
1158 |
+ |
|
1159 |
+}), { |
|
1160 |
+ MAX_URL_LENGTH: 2048, |
|
1161 |
+ |
|
1162 |
+ get: function(client, allowed, disabled, callback, context) { |
|
1163 |
+ var endpoint = client.endpoint; |
|
1164 |
+ |
|
1165 |
+ Faye.asyncEach(this._transports, function(pair, resume) { |
|
1166 |
+ var connType = pair[0], klass = pair[1], |
|
1167 |
+ connEndpoint = client.endpoints[connType] || endpoint; |
|
1168 |
+ |
|
1169 |
+ if (Faye.indexOf(disabled, connType) >= 0) |
|
1170 |
+ return resume(); |
|
1171 |
+ |
|
1172 |
+ if (Faye.indexOf(allowed, connType) < 0) { |
|
1173 |
+ klass.isUsable(client, connEndpoint, function() {}); |
|
1174 |
+ return resume(); |
|
1175 |
+ } |
|
1176 |
+ |
|
1177 |
+ klass.isUsable(client, connEndpoint, function(isUsable) { |
|
1178 |
+ if (!isUsable) return resume(); |
|
1179 |
+ var transport = klass.hasOwnProperty('create') ? klass.create(client, connEndpoint) : new klass(client, connEndpoint); |
|
1180 |
+ callback.call(context, transport); |
|
1181 |
+ }); |
|
1182 |
+ }, function() { |
|
1183 |
+ throw new Error('Could not find a usable connection type for ' + endpoint); |
|
1184 |
+ }); |
|
1185 |
+ }, |
|
1186 |
+ |
|
1187 |
+ register: function(type, klass) { |
|
1188 |
+ this._transports.push([type, klass]); |
|
1189 |
+ klass.prototype.connectionType = type; |
|
1190 |
+ }, |
|
1191 |
+ |
|
1192 |
+ _transports: [] |
|
1193 |
+}); |
|
1194 |
+ |
|
1195 |
+Faye.extend(Faye.Transport.prototype, Faye.Logging); |
|
1196 |
+Faye.extend(Faye.Transport.prototype, Faye.Publisher); |
|
1197 |
+Faye.extend(Faye.Transport.prototype, Faye.Timeouts); |
|
1198 |
+ |
|
1199 |
+ |
|
1200 |
+Faye.Event = { |
|
1201 |
+ _registry: [], |
|
1202 |
+ |
|
1203 |
+ on: function(element, eventName, callback, context) { |
|
1204 |
+ var wrapped = function() { callback.call(context) }; |
|
1205 |
+ |
|
1206 |
+ if (element.addEventListener) |
|
1207 |
+ element.addEventListener(eventName, wrapped, false); |
|
1208 |
+ else |
|
1209 |
+ element.attachEvent('on' + eventName, wrapped); |
|
1210 |
+ |
|
1211 |
+ this._registry.push({ |
|
1212 |
+ _element: element, |
|
1213 |
+ _type: eventName, |
|
1214 |
+ _callback: callback, |
|
1215 |
+ _context: context, |
|
1216 |
+ _handler: wrapped |
|
1217 |
+ }); |
|
1218 |
+ }, |
|
1219 |
+ |
|
1220 |
+ detach: function(element, eventName, callback, context) { |
|
1221 |
+ var i = this._registry.length, register; |
|
1222 |
+ while (i--) { |
|
1223 |
+ register = this._registry[i]; |
|
1224 |
+ |
|
1225 |
+ if ((element && element !== register._element) || |
|
1226 |
+ (eventName && eventName !== register._type) || |
|
1227 |
+ (callback && callback !== register._callback) || |
|
1228 |
+ (context && context !== register._context)) |
|
1229 |
+ continue; |
|
1230 |
+ |
|
1231 |
+ if (register._element.removeEventListener) |
|
1232 |
+ register._element.removeEventListener(register._type, register._handler, false); |
|
1233 |
+ else |
|
1234 |
+ register._element.detachEvent('on' + register._type, register._handler); |
|
1235 |
+ |
|
1236 |
+ this._registry.splice(i,1); |
|
1237 |
+ register = null; |
|
1238 |
+ } |
|
1239 |
+ } |
|
1240 |
+}; |
|
1241 |
+ |
|
1242 |
+Faye.Event.on(Faye.ENV, 'unload', Faye.Event.detach, Faye.Event); |
|
1243 |
+ |
|
1244 |
+ |
|
1245 |
+Faye.URI = Faye.extend(Faye.Class({ |
|
1246 |
+ queryString: function() { |
|
1247 |
+ var pairs = []; |
|
1248 |
+ for (var key in this.params) { |
|
1249 |
+ if (!this.params.hasOwnProperty(key)) continue; |
|
1250 |
+ pairs.push(encodeURIComponent(key) + '=' + encodeURIComponent(this.params[key])); |
|
1251 |
+ } |
|
1252 |
+ return pairs.join('&'); |
|
1253 |
+ }, |
|
1254 |
+ |
|
1255 |
+ isSameOrigin: function() { |
|
1256 |
+ var host = Faye.URI.parse(Faye.ENV.location.href, false); |
|
1257 |
+ |
|
1258 |
+ var external = (host.hostname !== this.hostname) || |
|
1259 |
+ (host.port !== this.port) || |
|
1260 |
+ (host.protocol !== this.protocol); |
|
1261 |
+ |
|
1262 |
+ return !external; |
|
1263 |
+ }, |
|
1264 |
+ |
|
1265 |
+ toURL: function() { |
|
1266 |
+ var query = this.queryString(); |
|
1267 |
+ return this.protocol + '//' + this.hostname + (this.port ? ':' + this.port : '') + |
|
1268 |
+ this.pathname + (query ? '?' + query : '') + this.hash; |
|
1269 |
+ } |
|
1270 |
+}), { |
|
1271 |
+ parse: function(url, params) { |
|
1272 |
+ if (typeof url !== 'string') return url; |
|
1273 |
+ var uri = new this(), parts; |
|
1274 |
+ |
|
1275 |
+ var consume = function(name, pattern, infer) { |
|
1276 |
+ url = url.replace(pattern, function(match) { |
|
1277 |
+ uri[name] = match; |
|
1278 |
+ return ''; |
|
1279 |
+ }); |
|
1280 |
+ if (uri[name] === undefined) |
|
1281 |
+ uri[name] = infer ? Faye.ENV.location[name] : ''; |
|
1282 |
+ }; |
|
1283 |
+ |
|
1284 |
+ consume('protocol', /^https?\:/, true); |
|
1285 |
+ consume('host', /^\/\/[^\/]+/, true); |
|
1286 |
+ |
|
1287 |
+ if (!/^\//.test(url)) url = Faye.ENV.location.pathname.replace(/[^\/]*$/, '') + url; |
|
1288 |
+ consume('pathname', /^\/[^\?#]*/); |
|
1289 |
+ consume('search', /^\?[^#]*/); |
|
1290 |
+ consume('hash', /^#.*/); |
|
1291 |
+ |
|
1292 |
+ if (/^\/\//.test(uri.host)) { |
|
1293 |
+ uri.host = uri.host.substr(2); |
|
1294 |
+ parts = uri.host.split(':'); |
|
1295 |
+ uri.hostname = parts[0]; |
|
1296 |
+ uri.port = parts[1] || ''; |
|
1297 |
+ } else { |
|
1298 |
+ uri.hostname = Faye.ENV.location.hostname; |
|
1299 |
+ uri.port = Faye.ENV.location.port; |
|
1300 |
+ } |
|
1301 |
+ |
|
1302 |
+ if (params === false) { |
|
1303 |
+ uri.params = {}; |
|
1304 |
+ } else { |
|
1305 |
+ var query = uri.search.replace(/^\?/, ''), |
|
1306 |
+ pairs = query ? query.split('&') : [], |
|
1307 |
+ n = pairs.length, |
|
1308 |
+ data = {}; |
|
1309 |
+ |
|
1310 |
+ while (n--) { |
|
1311 |
+ parts = pairs[n].split('='); |
|
1312 |
+ data[decodeURIComponent(parts[0] || '')] = decodeURIComponent(parts[1] || ''); |
|
1313 |
+ } |
|
1314 |
+ if (typeof params === 'object') Faye.extend(data, params); |
|
1315 |
+ |
|
1316 |
+ uri.params = data; |
|
1317 |
+ } |
|
1318 |
+ |
|
1319 |
+ return uri; |
|
1320 |
+ } |
|
1321 |
+}); |
|
1322 |
+ |
|
1323 |
+ |
|
1324 |
+/* |
|
1325 |
+ http://www.JSON.org/json2.js |
|
1326 |
+ 2009-04-16 |
|
1327 |
+ |
|
1328 |
+ Public Domain. |
|
1329 |
+ |
|
1330 |
+ NO WARRANTY EXPRESSED OR IMPLIED. USE AT YOUR OWN RISK. |
|
1331 |
+ |
|
1332 |
+ See http://www.JSON.org/js.html |
|
1333 |
+ |
|
1334 |
+ This file creates a global JSON object containing two methods: stringify |
|
1335 |
+ and parse. |
|
1336 |
+ |
|
1337 |
+ JSON.stringify(value, replacer, space) |
|
1338 |
+ value any JavaScript value, usually an object or array. |
|
1339 |
+ |
|
1340 |
+ replacer an optional parameter that determines how object |
|
1341 |
+ values are stringified for objects. It can be a |
|
1342 |
+ function or an array of strings. |
|
1343 |
+ |
|
1344 |
+ space an optional parameter that specifies the indentation |
|
1345 |
+ of nested structures. If it is omitted, the text will |
|
1346 |
+ be packed without extra whitespace. If it is a number, |
|
1347 |
+ it will specify the number of spaces to indent at each |
|
1348 |
+ level. If it is a string (such as '\t' or ' '), |
|
1349 |
+ it contains the characters used to indent at each level. |
|
1350 |
+ |
|
1351 |
+ This method produces a JSON text from a JavaScript value. |
|
1352 |
+ |
|
1353 |
+ When an object value is found, if the object contains a toJSON |
|
1354 |
+ method, its toJSON method will be called and the result will be |
|
1355 |
+ stringified. A toJSON method does not serialize: it returns the |
|
1356 |
+ value represented by the name/value pair that should be serialized, |
|
1357 |
+ or undefined if nothing should be serialized. The toJSON method |
|
1358 |
+ will be passed the key associated with the value, and this will be |
|
1359 |
+ bound to the object holding the key. |
|
1360 |
+ |
|
1361 |
+ For example, this would serialize Dates as ISO strings. |
|
1362 |
+ |
|
1363 |
+ Date.prototype.toJSON = function (key) { |
|
1364 |
+ function f(n) { |
|
1365 |
+ // Format integers to have at least two digits. |
|
1366 |
+ return n < 10 ? '0' + n : n; |
|
1367 |
+ } |
|
1368 |
+ |
|
1369 |
+ return this.getUTCFullYear() + '-' + |
|
1370 |
+ f(this.getUTCMonth() + 1) + '-' + |
|
1371 |
+ f(this.getUTCDate()) + 'T' + |
|
1372 |
+ f(this.getUTCHours()) + ':' + |
|
1373 |
+ f(this.getUTCMinutes()) + ':' + |
|
1374 |
+ f(this.getUTCSeconds()) + 'Z'; |
|
1375 |
+ }; |
|
1376 |
+ |
|
1377 |
+ You can provide an optional replacer method. It will be passed the |
|
1378 |
+ key and value of each member, with this bound to the containing |
|
1379 |
+ object. The value that is returned from your method will be |
|
1380 |
+ serialized. If your method returns undefined, then the member will |
|
1381 |
+ be excluded from the serialization. |
|
1382 |
+ |
|
1383 |
+ If the replacer parameter is an array of strings, then it will be |
|
1384 |
+ used to select the members to be serialized. It filters the results |
|
1385 |
+ such that only members with keys listed in the replacer array are |
|
1386 |
+ stringified. |
|
1387 |
+ |
|
1388 |
+ Values that do not have JSON representations, such as undefined or |
|
1389 |
+ functions, will not be serialized. Such values in objects will be |
|
1390 |
+ dropped; in arrays they will be replaced with null. You can use |
|
1391 |
+ a replacer function to replace those with JSON values. |
|
1392 |
+ JSON.stringify(undefined) returns undefined. |
|
1393 |
+ |
|
1394 |
+ The optional space parameter produces a stringification of the |
|
1395 |
+ value that is filled with line breaks and indentation to make it |
|
1396 |
+ easier to read. |
|
1397 |
+ |
|
1398 |
+ If the space parameter is a non-empty string, then that string will |
|
1399 |
+ be used for indentation. If the space parameter is a number, then |
|
1400 |
+ the indentation will be that many spaces. |
|
1401 |
+ |
|
1402 |
+ Example: |
|
1403 |
+ |
|
1404 |
+ text = JSON.stringify(['e', {pluribus: 'unum'}]); |
|
1405 |
+ // text is '["e",{"pluribus":"unum"}]' |
|
1406 |
+ |
|
1407 |
+ |
|
1408 |
+ text = JSON.stringify(['e', {pluribus: 'unum'}], null, '\t'); |
|
1409 |
+ // text is '[\n\t"e",\n\t{\n\t\t"pluribus": "unum"\n\t}\n]' |
|
1410 |
+ |
|
1411 |
+ text = JSON.stringify([new Date()], function (key, value) { |
|
1412 |
+ return this[key] instanceof Date ? |
|
1413 |
+ 'Date(' + this[key] + ')' : value; |
|
1414 |
+ }); |
|
1415 |
+ // text is '["Date(---current time---)"]' |
|
1416 |
+ |
|
1417 |
+ |
|
1418 |
+ JSON.parse(text, reviver) |
|
1419 |
+ This method parses a JSON text to produce an object or array. |
|
1420 |
+ It can throw a SyntaxError exception. |
|
1421 |
+ |
|
1422 |
+ The optional reviver parameter is a function that can filter and |
|
1423 |
+ transform the results. It receives each of the keys and values, |
|
1424 |
+ and its return value is used instead of the original value. |
|
1425 |
+ If it returns what it received, then the structure is not modified. |
|
1426 |
+ If it returns undefined then the member is deleted. |
|
1427 |
+ |
|
1428 |
+ Example: |
|
1429 |
+ |
|
1430 |
+ // Parse the text. Values that look like ISO date strings will |
|
1431 |
+ // be converted to Date objects. |
|
1432 |
+ |
|
1433 |
+ myData = JSON.parse(text, function (key, value) { |
|
1434 |
+ var a; |
|
1435 |
+ if (typeof value === 'string') { |
|
1436 |
+ a = |
|
1437 |
+/^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2}(?:\.\d*)?)Z$/.exec(value); |
|
1438 |
+ if (a) { |
|
1439 |
+ return new Date(Date.UTC(+a[1], +a[2] - 1, +a[3], +a[4], |
|
1440 |
+ +a[5], +a[6])); |
|
1441 |
+ } |
|
1442 |
+ } |
|
1443 |
+ return value; |
|
1444 |
+ }); |
|
1445 |
+ |
|
1446 |
+ myData = JSON.parse('["Date(09/09/2001)"]', function (key, value) { |
|
1447 |
+ var d; |
|
1448 |
+ if (typeof value === 'string' && |
|
1449 |
+ value.slice(0, 5) === 'Date(' && |
|
1450 |
+ value.slice(-1) === ')') { |
|
1451 |
+ d = new Date(value.slice(5, -1)); |
|
1452 |
+ if (d) { |
|
1453 |
+ return d; |
|
1454 |
+ } |
|
1455 |
+ } |
|
1456 |
+ return value; |
|
1457 |
+ }); |
|
1458 |
+ |
|
1459 |
+ |
|
1460 |
+ This is a reference implementation. You are free to copy, modify, or |
|
1461 |
+ redistribute. |
|
1462 |
+ |
|
1463 |
+ This code should be minified before deployment. |
|
1464 |
+ See http://javascript.crockford.com/jsmin.html |
|
1465 |
+ |
|
1466 |
+ USE YOUR OWN COPY. IT IS EXTREMELY UNWISE TO LOAD CODE FROM SERVERS YOU DO |
|
1467 |
+ NOT CONTROL. |
|
1468 |
+*/ |
|
1469 |
+ |
|
1470 |
+/*jslint evil: true */ |
|
1471 |
+ |
|
1472 |
+/*global JSON */ |
|
1473 |
+ |
|
1474 |
+/*members "", "\b", "\t", "\n", "\f", "\r", "\"", JSON, "\\", apply, |
|
1475 |
+ call, charCodeAt, getUTCDate, getUTCFullYear, getUTCHours, |
|
1476 |
+ getUTCMinutes, getUTCMonth, getUTCSeconds, hasOwnProperty, join, |
|
1477 |
+ lastIndex, length, parse, prototype, push, replace, slice, stringify, |
|
1478 |
+ test, toJSON, toString, valueOf |
|
1479 |
+*/ |
|
1480 |
+ |
|
1481 |
+// Create a JSON object only if one does not already exist. We create the |
|
1482 |
+// methods in a closure to avoid creating global variables. |
|
1483 |
+ |
|
1484 |
+if (!this.JSON) { |
|
1485 |
+ JSON = {}; |
|
1486 |
+} |
|
1487 |
+(function () { |
|
1488 |
+ |
|
1489 |
+ function f(n) { |
|
1490 |
+ // Format integers to have at least two digits. |
|
1491 |
+ return n < 10 ? '0' + n : n; |
|
1492 |
+ } |
|
1493 |
+ |
|
1494 |
+ if (typeof Date.prototype.toJSON !== 'function') { |
|
1495 |
+ |
|
1496 |
+ Date.prototype.toJSON = function (key) { |
|
1497 |
+ |
|
1498 |
+ return this.getUTCFullYear() + '-' + |
|
1499 |
+ f(this.getUTCMonth() + 1) + '-' + |
|
1500 |
+ f(this.getUTCDate()) + 'T' + |
|
1501 |
+ f(this.getUTCHours()) + ':' + |
|
1502 |
+ f(this.getUTCMinutes()) + ':' + |
|
1503 |
+ f(this.getUTCSeconds()) + 'Z'; |
|
1504 |
+ }; |
|
1505 |
+ |
|
1506 |
+ String.prototype.toJSON = |
|
1507 |
+ Number.prototype.toJSON = |
|
1508 |
+ Boolean.prototype.toJSON = function (key) { |
|
1509 |
+ return this.valueOf(); |
|
1510 |
+ }; |
|
1511 |
+ } |
|
1512 |
+ |
|
1513 |
+ var cx = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g, |
|
1514 |
+ escapable = /[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g, |
|
1515 |
+ gap, |
|
1516 |
+ indent, |
|
1517 |
+ meta = { // table of character substitutions |
|
1518 |
+ '\b': '\\b', |
|
1519 |
+ '\t': '\\t', |
|
1520 |
+ '\n': '\\n', |
|
1521 |
+ '\f': '\\f', |
|
1522 |
+ '\r': '\\r', |
|
1523 |
+ '"' : '\\"', |
|
1524 |
+ '\\': '\\\\' |
|
1525 |
+ }, |
|
1526 |
+ rep; |
|
1527 |
+ |
|
1528 |
+ |
|
1529 |
+ function quote(string) { |
|
1530 |
+ |
|
1531 |
+// If the string contains no control characters, no quote characters, and no |
|
1532 |
+// backslash characters, then we can safely slap some quotes around it. |
|
1533 |
+// Otherwise we must also replace the offending characters with safe escape |
|
1534 |
+// sequences. |
|
1535 |
+ |
|
1536 |
+ escapable.lastIndex = 0; |
|
1537 |
+ return escapable.test(string) ? |
|
1538 |
+ '"' + string.replace(escapable, function (a) { |
|
1539 |
+ var c = meta[a]; |
|
1540 |
+ return typeof c === 'string' ? c : |
|
1541 |
+ '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4); |
|
1542 |
+ }) + '"' : |
|
1543 |
+ '"' + string + '"'; |
|
1544 |
+ } |
|
1545 |
+ |
|
1546 |
+ |
|
1547 |
+ function str(key, holder) { |
|
1548 |
+ |
|
1549 |
+// Produce a string from holder[key]. |
|
1550 |
+ |
|
1551 |
+ var i, // The loop counter. |
|
1552 |
+ k, // The member key. |
|
1553 |
+ v, // The member value. |
|
1554 |
+ length, |
|
1555 |
+ mind = gap, |
|
1556 |
+ partial, |
|
1557 |
+ value = holder[key]; |
|
1558 |
+ |
|
1559 |
+// If the value has a toJSON method, call it to obtain a replacement value. |
|
1560 |
+ |
|
1561 |
+ if (value && typeof value === 'object' && |
|
1562 |
+ typeof value.toJSON === 'function') { |
|
1563 |
+ value = value.toJSON(key); |
|
1564 |
+ } |
|
1565 |
+ |
|
1566 |
+// If we were called with a replacer function, then call the replacer to |
|
1567 |
+// obtain a replacement value. |
|
1568 |
+ |
|
1569 |
+ if (typeof rep === 'function') { |
|
1570 |
+ value = rep.call(holder, key, value); |
|
1571 |
+ } |
|
1572 |
+ |
|
1573 |
+// What happens next depends on the value's type. |
|
1574 |
+ |
|
1575 |
+ switch (typeof value) { |
|
1576 |
+ case 'string': |
|
1577 |
+ return quote(value); |
|
1578 |
+ |
|
1579 |
+ case 'number': |
|
1580 |
+ |
|
1581 |
+// JSON numbers must be finite. Encode non-finite numbers as null. |
|
1582 |
+ |
|
1583 |
+ return isFinite(value) ? String(value) : 'null'; |
|
1584 |
+ |
|
1585 |
+ case 'boolean': |
|
1586 |
+ case 'null': |
|
1587 |
+ |
|
1588 |
+// If the value is a boolean or null, convert it to a string. Note: |
|
1589 |
+// typeof null does not produce 'null'. The case is included here in |
|
1590 |
+// the remote chance that this gets fixed someday. |
|
1591 |
+ |
|
1592 |
+ return String(value); |
|
1593 |
+ |
|
1594 |
+// If the type is 'object', we might be dealing with an object or an array or |
|
1595 |
+// null. |
|
1596 |
+ |
|
1597 |
+ case 'object': |
|
1598 |
+ |
|
1599 |
+// Due to a specification blunder in ECMAScript, typeof null is 'object', |
|
1600 |
+// so watch out for that case. |
|
1601 |
+ |
|
1602 |
+ if (!value) { |
|
1603 |
+ return 'null'; |
|
1604 |
+ } |
|
1605 |
+ |
|
1606 |
+// Make an array to hold the partial results of stringifying this object value. |
|
1607 |
+ |
|
1608 |
+ gap += indent; |
|
1609 |
+ partial = []; |
|
1610 |
+ |
|
1611 |
+// Is the value an array? |
|
1612 |
+ |
|
1613 |
+ if (Object.prototype.toString.apply(value) === '[object Array]') { |
|
1614 |
+ |
|
1615 |
+// The value is an array. Stringify every element. Use null as a placeholder |
|
1616 |
+// for non-JSON values. |
|
1617 |
+ |
|
1618 |
+ length = value.length; |
|
1619 |
+ for (i = 0; i < length; i += 1) { |
|
1620 |
+ partial[i] = str(i, value) || 'null'; |
|
1621 |
+ } |
|
1622 |
+ |
|
1623 |
+// Join all of the elements together, separated with commas, and wrap them in |
|
1624 |
+// brackets. |
|
1625 |
+ |
|
1626 |
+ v = partial.length === 0 ? '[]' : |
|
1627 |
+ gap ? '[\n' + gap + |
|
1628 |
+ partial.join(',\n' + gap) + '\n' + |
|
1629 |
+ mind + ']' : |
|
1630 |
+ '[' + partial.join(',') + ']'; |
|
1631 |
+ gap = mind; |
|
1632 |
+ return v; |
|
1633 |
+ } |
|
1634 |
+ |
|
1635 |
+// If the replacer is an array, use it to select the members to be stringified. |
|
1636 |
+ |
|
1637 |
+ if (rep && typeof rep === 'object') { |
|
1638 |
+ length = rep.length; |
|
1639 |
+ for (i = 0; i < length; i += 1) { |
|
1640 |
+ k = rep[i]; |
|
1641 |
+ if (typeof k === 'string') { |
|
1642 |
+ v = str(k, value); |
|
1643 |
+ if (v) { |
|
1644 |
+ partial.push(quote(k) + (gap ? ': ' : ':') + v); |
|
1645 |
+ } |
|
1646 |
+ } |
|
1647 |
+ } |
|
1648 |
+ } else { |
|
1649 |
+ |
|
1650 |
+// Otherwise, iterate through all of the keys in the object. |
|
1651 |
+ |
|
1652 |
+ for (k in value) { |
|
1653 |
+ if (Object.hasOwnProperty.call(value, k)) { |
|
1654 |
+ v = str(k, value); |
|
1655 |
+ if (v) { |
|
1656 |
+ partial.push(quote(k) + (gap ? ': ' : ':') + v); |
|
1657 |
+ } |
|
1658 |
+ } |
|
1659 |
+ } |
|
1660 |
+ } |
|
1661 |
+ |
|
1662 |
+// Join all of the member texts together, separated with commas, |
|
1663 |
+// and wrap them in braces. |
|
1664 |
+ |
|
1665 |
+ v = partial.length === 0 ? '{}' : |
|
1666 |
+ gap ? '{\n' + gap + partial.join(',\n' + gap) + '\n' + |
|
1667 |
+ mind + '}' : '{' + partial.join(',') + '}'; |
|
1668 |
+ gap = mind; |
|
1669 |
+ return v; |
|
1670 |
+ } |
|
1671 |
+ } |
|
1672 |
+ |
|
1673 |
+// If the JSON object does not yet have a stringify method, give it one. |
|
1674 |
+ |
|
1675 |
+// NOTE we've hacked this to expose this method to Faye. We need to use this |
|
1676 |
+// to avoid problems with buggy Firefox version and bad #toJSON implementations |
|
1677 |
+ |
|
1678 |
+ Faye.stringify = function (value, replacer, space) { |
|
1679 |
+ |
|
1680 |
+// The stringify method takes a value and an optional replacer, and an optional |
|
1681 |
+// space parameter, and returns a JSON text. The replacer can be a function |
|
1682 |
+// that can replace values, or an array of strings that will select the keys. |
|
1683 |
+// A default replacer method can be provided. Use of the space parameter can |
|
1684 |
+// produce text that is more easily readable. |
|
1685 |
+ |
|
1686 |
+ var i; |
|
1687 |
+ gap = ''; |
|
1688 |
+ indent = ''; |
|
1689 |
+ |
|
1690 |
+// If the space parameter is a number, make an indent string containing that |
|
1691 |
+// many spaces. |
|
1692 |
+ |
|
1693 |
+ if (typeof space === 'number') { |
|
1694 |
+ for (i = 0; i < space; i += 1) { |
|
1695 |
+ indent += ' '; |
|
1696 |
+ } |
|
1697 |
+ |
|
1698 |
+// If the space parameter is a string, it will be used as the indent string. |
|
1699 |
+ |
|
1700 |
+ } else if (typeof space === 'string') { |
|
1701 |
+ indent = space; |
|
1702 |
+ } |
|
1703 |
+ |
|
1704 |
+// If there is a replacer, it must be a function or an array. |
|
1705 |
+// Otherwise, throw an error. |
|
1706 |
+ |
|
1707 |
+ rep = replacer; |
|
1708 |
+ if (replacer && typeof replacer !== 'function' && |
|
1709 |
+ (typeof replacer !== 'object' || |
|
1710 |
+ typeof replacer.length !== 'number')) { |
|
1711 |
+ throw new Error('JSON.stringify'); |
|
1712 |
+ } |
|
1713 |
+ |
|
1714 |
+// Make a fake root object containing our value under the key of ''. |
|
1715 |
+// Return the result of stringifying the value. |
|
1716 |
+ |
|
1717 |
+ return str('', {'': value}); |
|
1718 |
+ }; |
|
1719 |
+ |
|
1720 |
+ if (typeof JSON.stringify !== 'function') { |
|
1721 |
+ JSON.stringify = Faye.stringify; |
|
1722 |
+ } |
|
1723 |
+ |
|
1724 |
+ |
|
1725 |
+// If the JSON object does not yet have a parse method, give it one. |
|
1726 |
+ |
|
1727 |
+ if (typeof JSON.parse !== 'function') { |
|
1728 |
+ JSON.parse = function (text, reviver) { |
|
1729 |
+ |
|
1730 |
+// The parse method takes a text and an optional reviver function, and returns |
|
1731 |
+// a JavaScript value if the text is a valid JSON text. |
|
1732 |
+ |
|
1733 |
+ var j; |
|
1734 |
+ |
|
1735 |
+ function walk(holder, key) { |
|
1736 |
+ |
|
1737 |
+// The walk method is used to recursively walk the resulting structure so |
|
1738 |
+// that modifications can be made. |
|
1739 |
+ |
|
1740 |
+ var k, v, value = holder[key]; |
|
1741 |
+ if (value && typeof value === 'object') { |
|
1742 |
+ for (k in value) { |
|
1743 |
+ if (Object.hasOwnProperty.call(value, k)) { |
|
1744 |
+ v = walk(value, k); |
|
1745 |
+ if (v !== undefined) { |
|
1746 |
+ value[k] = v; |
|
1747 |
+ } else { |
|
1748 |
+ delete value[k]; |
|
1749 |
+ } |
|
1750 |
+ } |
|
1751 |
+ } |
|
1752 |
+ } |
|
1753 |
+ return reviver.call(holder, key, value); |
|
1754 |
+ } |
|
1755 |
+ |
|
1756 |
+ |
|
1757 |
+// Parsing happens in four stages. In the first stage, we replace certain |
|
1758 |
+// Unicode characters with escape sequences. JavaScript handles many characters |
|
1759 |
+// incorrectly, either silently deleting them, or treating them as line endings. |
|
1760 |
+ |
|
1761 |
+ cx.lastIndex = 0; |
|
1762 |
+ if (cx.test(text)) { |
|
1763 |
+ text = text.replace(cx, function (a) { |
|
1764 |
+ return '\\u' + |
|
1765 |
+ ('0000' + a.charCodeAt(0).toString(16)).slice(-4); |
|
1766 |
+ }); |
|
1767 |
+ } |
|
1768 |
+ |
|
1769 |
+// In the second stage, we run the text against regular expressions that look |
|
1770 |
+// for non-JSON patterns. We are especially concerned with '()' and 'new' |
|
1771 |
+// because they can cause invocation, and '=' because it can cause mutation. |
|
1772 |
+// But just to be safe, we want to reject all unexpected forms. |
|
1773 |
+ |
|
1774 |
+// We split the second stage into 4 regexp operations in order to work around |
|
1775 |
+// crippling inefficiencies in IE's and Safari's regexp engines. First we |
|
1776 |
+// replace the JSON backslash pairs with '@' (a non-JSON character). Second, we |
|
1777 |
+// replace all simple value tokens with ']' characters. Third, we delete all |
|
1778 |
+// open brackets that follow a colon or comma or that begin the text. Finally, |
|
1779 |
+// we look to see that the remaining characters are only whitespace or ']' or |
|
1780 |
+// ',' or ':' or '{' or '}'. If that is so, then the text is safe for eval. |
|
1781 |
+ |
|
1782 |
+ if (/^[\],:{}\s]*$/. |
|
1783 |
+test(text.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, '@'). |
|
1784 |
+replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']'). |
|
1785 |
+replace(/(?:^|:|,)(?:\s*\[)+/g, ''))) { |
|
1786 |
+ |
|
1787 |
+// In the third stage we use the eval function to compile the text into a |
|
1788 |
+// JavaScript structure. The '{' operator is subject to a syntactic ambiguity |
|
1789 |
+// in JavaScript: it can begin a block or an object literal. We wrap the text |
|
1790 |
+// in parens to eliminate the ambiguity. |
|
1791 |
+ |
|
1792 |
+ j = eval('(' + text + ')'); |
|
1793 |
+ |
|
1794 |
+// In the optional fourth stage, we recursively walk the new structure, passing |
|
1795 |
+// each name/value pair to a reviver function for possible transformation. |
|
1796 |
+ |
|
1797 |
+ return typeof reviver === 'function' ? |
|
1798 |
+ walk({'': j}, '') : j; |
|
1799 |
+ } |
|
1800 |
+ |
|
1801 |
+// If the text is not JSON parseable, then a SyntaxError is thrown. |
|
1802 |
+ |
|
1803 |
+ throw new SyntaxError('JSON.parse'); |
|
1804 |
+ }; |
|
1805 |
+ } |
|
1806 |
+}()); |
|
1807 |
+ |
|
1808 |
+ |
|
1809 |
+Faye.Transport.WebSocket = Faye.extend(Faye.Class(Faye.Transport, { |
|
1810 |
+ UNCONNECTED: 1, |
|
1811 |
+ CONNECTING: 2, |
|
1812 |
+ CONNECTED: 3, |
|
1813 |
+ |
|
1814 |
+ batching: false, |
|
1815 |
+ |
|
1816 |
+ isUsable: function(callback, context) { |
|
1817 |
+ this.callback(function() { callback.call(context, true) }); |
|
1818 |
+ this.errback(function() { callback.call(context, false) }); |
|
1819 |
+ this.connect(); |
|
1820 |
+ }, |
|
1821 |
+ |
|
1822 |
+ request: function(messages, timeout) { |
|
1823 |
+ if (messages.length === 0) return; |
|
1824 |
+ this._messages = this._messages || {}; |
|
1825 |
+ |
|
1826 |
+ for (var i = 0, n = messages.length; i < n; i++) { |
|
1827 |
+ this._messages[messages[i].id] = messages[i]; |
|
1828 |
+ } |
|
1829 |
+ this.callback(function(socket) { socket.send(Faye.toJSON(messages)) }); |
|
1830 |
+ this.connect(); |
|
1831 |
+ }, |
|
1832 |
+ |
|
1833 |
+ close: function() { |
|
1834 |
+ if (!this._socket) return; |
|
1835 |
+ this._socket.onclose = this._socket.onerror = null; |
|
1836 |
+ this._socket.close(); |
|
1837 |
+ delete this._socket; |
|
1838 |
+ this.setDeferredStatus('deferred'); |
|
1839 |
+ this._state = this.UNCONNECTED; |
|
1840 |
+ }, |
|
1841 |
+ |
|
1842 |
+ connect: function() { |
|
1843 |
+ if (Faye.Transport.WebSocket._unloaded) return; |
|
1844 |
+ |
|
1845 |
+ this._state = this._state || this.UNCONNECTED; |
|
1846 |
+ if (this._state !== this.UNCONNECTED) return; |
|
1847 |
+ |
|
1848 |
+ this._state = this.CONNECTING; |
|
1849 |
+ |
|
1850 |
+ var ws = Faye.Transport.WebSocket.getClass(); |
|
1851 |
+ if (!ws) return this.setDeferredStatus('failed'); |
|
1852 |
+ |
|
1853 |
+ this._socket = new ws(Faye.Transport.WebSocket.getSocketUrl(this.endpoint)); |
|
1854 |
+ var self = this; |
|
1855 |
+ |
|
1856 |
+ this._socket.onopen = function() { |
|
1857 |
+ self._state = self.CONNECTED; |
|
1858 |
+ self._everConnected = true; |
|
1859 |
+ self.setDeferredStatus('succeeded', self._socket); |
|
1860 |
+ self.trigger('up'); |
|
1861 |
+ }; |
|
1862 |
+ |
|
1863 |
+ this._socket.onmessage = function(event) { |
|
1864 |
+ var messages = JSON.parse(event.data); |
|
1865 |
+ if (!messages) return; |
|
1866 |
+ messages = [].concat(messages); |
|
1867 |
+ |
|
1868 |
+ for (var i = 0, n = messages.length; i < n; i++) { |
|
1869 |
+ delete self._messages[messages[i].id]; |
|
1870 |
+ } |
|
1871 |
+ self.receive(messages); |
|
1872 |
+ }; |
|
1873 |
+ |
|
1874 |
+ this._socket.onclose = this._socket.onerror = function() { |
|
1875 |
+ var wasConnected = (self._state === self.CONNECTED); |
|
1876 |
+ self.setDeferredStatus('deferred'); |
|
1877 |
+ self._state = self.UNCONNECTED; |
|
1878 |
+ |
|
1879 |
+ self.close(); |
|
1880 |
+ |
|
1881 |
+ if (wasConnected) return self.resend(); |
|
1882 |
+ if (!self._everConnected) return self.setDeferredStatus('failed'); |
|
1883 |
+ |
|
1884 |
+ var retry = self._client.retry * 1000; |
|
1885 |
+ Faye.ENV.setTimeout(function() { self.connect() }, retry); |
|
1886 |
+ self.trigger('down'); |
|
1887 |
+ }; |
|
1888 |
+ }, |
|
1889 |
+ |
|
1890 |
+ resend: function() { |
|
1891 |
+ if (!this._messages) return; |
|
1892 |
+ var messages = Faye.map(this._messages, function(id, msg) { return msg }); |
|
1893 |
+ this.request(messages); |
|
1894 |
+ } |
|
1895 |
+}), { |
|
1896 |
+ getSocketUrl: function(endpoint) { |
|
1897 |
+ if (Faye.URI) endpoint = Faye.URI.parse(endpoint).toURL(); |
|
1898 |
+ return endpoint.replace(/^http(s?):/ig, 'ws$1:'); |
|
1899 |
+ }, |
|
1900 |
+ |
|
1901 |
+ getClass: function() { |
|
1902 |
+ return (Faye.WebSocket && Faye.WebSocket.Client) || |
|
1903 |
+ Faye.ENV.WebSocket || |
|
1904 |
+ Faye.ENV.MozWebSocket; |
|
1905 |
+ }, |
|
1906 |
+ |
|
1907 |
+ isUsable: function(client, endpoint, callback, context) { |
|
1908 |
+ this.create(client, endpoint).isUsable(callback, context); |
|
1909 |
+ }, |
|
1910 |
+ |
|
1911 |
+ create: function(client, endpoint) { |
|
1912 |
+ var sockets = client.transports.websocket = client.transports.websocket || {}; |
|
1913 |
+ sockets[endpoint] = sockets[endpoint] || new this(client, endpoint); |
|
1914 |
+ return sockets[endpoint]; |
|
1915 |
+ } |
|
1916 |
+}); |
|
1917 |
+ |
|
1918 |
+Faye.extend(Faye.Transport.WebSocket.prototype, Faye.Deferrable); |
|
1919 |
+Faye.Transport.register('websocket', Faye.Transport.WebSocket); |
|
1920 |
+ |
|
1921 |
+if (Faye.Event) |
|
1922 |
+ Faye.Event.on(Faye.ENV, 'beforeunload', function() { |
|
1923 |
+ Faye.Transport.WebSocket._unloaded = true; |
|
1924 |
+ }); |
|
1925 |
+ |
|
1926 |
+ |
|
1927 |
+Faye.Transport.EventSource = Faye.extend(Faye.Class(Faye.Transport, { |
|
1928 |
+ initialize: function(client, endpoint) { |
|
1929 |
+ Faye.Transport.prototype.initialize.call(this, client, endpoint); |
|
1930 |
+ if (!Faye.ENV.EventSource) return this.setDeferredStatus('failed'); |
|
1931 |
+ |
|
1932 |
+ this._xhr = new Faye.Transport.XHR(client, endpoint); |
|
1933 |
+ |
|
1934 |
+ var socket = new EventSource(endpoint + '/' + client.getClientId()), |
|
1935 |
+ self = this; |
|
1936 |
+ |
|
1937 |
+ socket.onopen = function() { |
|
1938 |
+ self._everConnected = true; |
|
1939 |
+ self.setDeferredStatus('succeeded'); |
|
1940 |
+ self.trigger('up'); |
|
1941 |
+ }; |
|
1942 |
+ |
|
1943 |
+ socket.onerror = function() { |
|
1944 |
+ if (self._everConnected) { |
|
1945 |
+ self.trigger('down'); |
|
1946 |
+ } else { |
|
1947 |
+ self.setDeferredStatus('failed'); |
|
1948 |
+ socket.close(); |
|
1949 |
+ } |
|
1950 |
+ }; |
|
1951 |
+ |
|
1952 |
+ socket.onmessage = function(event) { |
|
1953 |
+ self.receive(JSON.parse(event.data)); |
|
1954 |
+ self.trigger('up'); |
|
1955 |
+ }; |
|
1956 |
+ |
|
1957 |
+ this._socket = socket; |
|
1958 |
+ }, |
|
1959 |
+ |
|
1960 |
+ isUsable: function(callback, context) { |
|
1961 |
+ this.callback(function() { callback.call(context, true) }); |
|
1962 |
+ this.errback(function() { callback.call(context, false) }); |
|
1963 |
+ }, |
|
1964 |
+ |
|
1965 |
+ request: function(message, timeout) { |
|
1966 |
+ this._xhr.request(message, timeout); |
|
1967 |
+ }, |
|
1968 |
+ |
|
1969 |
+ close: function() { |
|
1970 |
+ if (!this._socket) return; |
|
1971 |
+ this._socket.onerror = null; |
|
1972 |
+ this._socket.close(); |
|
1973 |
+ delete this._socket; |
|
1974 |
+ } |
|
1975 |
+}), { |
|
1976 |
+ isUsable: function(client, endpoint, callback, context) { |
|
1977 |
+ var id = client.getClientId(); |
|
1978 |
+ if (!id) return callback.call(context, false); |
|
1979 |
+ |
|
1980 |
+ Faye.Transport.XHR.isUsable(client, endpoint, function(usable) { |
|
1981 |
+ if (!usable) return callback.call(context, false); |
|
1982 |
+ this.create(client, endpoint).isUsable(callback, context); |
|
1983 |
+ }, this); |
|
1984 |
+ }, |
|
1985 |
+ |
|
1986 |
+ create: function(client, endpoint) { |
|
1987 |
+ var sockets = client.transports.eventsource = client.transports.eventsource || {}, |
|
1988 |
+ id = client.getClientId(), |
|
1989 |
+ endpoint = endpoint + '/' + (id || ''); |
|
1990 |
+ |
|
1991 |
+ sockets[endpoint] = sockets[endpoint] || new this(client, endpoint); |
|
1992 |
+ return sockets[endpoint]; |
|
1993 |
+ } |
|
1994 |
+}); |
|
1995 |
+ |
|
1996 |
+Faye.extend(Faye.Transport.EventSource.prototype, Faye.Deferrable); |
|
1997 |
+Faye.Transport.register('eventsource', Faye.Transport.EventSource); |
|
1998 |
+ |
|
1999 |
+ |
|
2000 |
+Faye.Transport.XHR = Faye.extend(Faye.Class(Faye.Transport, { |
|
2001 |
+ request: function(message, timeout) { |
|
2002 |
+ var retry = this.retry(message, timeout), |
|
2003 |
+ path = Faye.URI.parse(this.endpoint).pathname, |
|
2004 |
+ self = this, |
|
2005 |
+ xhr = Faye.ENV.ActiveXObject |
|
2006 |
+ ? new ActiveXObject("Microsoft.XMLHTTP") |
|
2007 |
+ : new XMLHttpRequest(); |
|
2008 |
+ |
|
2009 |
+ xhr.open('POST', path, true); |
|
2010 |
+ xhr.setRequestHeader('Content-Type', 'application/json'); |
|
2011 |
+ xhr.setRequestHeader('Pragma', 'no-cache'); |
|
2012 |
+ xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest'); |
|
2013 |
+ |
|
2014 |
+ var headers = this.headers; |
|
2015 |
+ for (var key in headers) { |
|
2016 |
+ if (!headers.hasOwnProperty(key)) continue; |
|
2017 |
+ xhr.setRequestHeader(key, headers[key]); |
|
2018 |
+ } |
|
2019 |
+ |
|
2020 |
+ var abort = function() { xhr.abort() }; |
|
2021 |
+ Faye.Event.on(Faye.ENV, 'beforeunload', abort); |
|
2022 |
+ |
|
2023 |
+ var cleanUp = function() { |
|
2024 |
+ Faye.Event.detach(Faye.ENV, 'beforeunload', abort); |
|
2025 |
+ xhr.onreadystatechange = function() {}; |
|
2026 |
+ xhr = null; |
|
2027 |
+ }; |
|
2028 |
+ |
|
2029 |
+ xhr.onreadystatechange = function() { |
|
2030 |
+ if (xhr.readyState !== 4) return; |
|
2031 |
+ |
|
2032 |
+ var parsedMessage = null, |
|
2033 |
+ status = xhr.status, |
|
2034 |
+ successful = ((status >= 200 && status < 300) || |
|
2035 |
+ status === 304 || |
|
2036 |
+ status === 1223); |
|
2037 |
+ |
|
2038 |
+ if (!successful) { |
|
2039 |
+ cleanUp(); |
|
2040 |
+ retry(); |
|
2041 |
+ return self.trigger('down'); |
|
2042 |
+ } |
|
2043 |
+ |
|
2044 |
+ try { |
|
2045 |
+ parsedMessage = JSON.parse(xhr.responseText); |
|
2046 |
+ } catch (e) {} |
|
2047 |
+ |
|
2048 |
+ cleanUp(); |
|
2049 |
+ |
|
2050 |
+ if (parsedMessage) { |
|
2051 |
+ self.receive(parsedMessage); |
|
2052 |
+ self.trigger('up'); |
|
2053 |
+ } else { |
|
2054 |
+ retry(); |
|
2055 |
+ self.trigger('down'); |
|
2056 |
+ } |
|
2057 |
+ }; |
|
2058 |
+ |
|
2059 |
+ xhr.send(Faye.toJSON(message)); |
|
2060 |
+ } |
|
2061 |
+}), { |
|
2062 |
+ isUsable: function(client, endpoint, callback, context) { |
|
2063 |
+ callback.call(context, Faye.URI.parse(endpoint).isSameOrigin()); |
|
2064 |
+ } |
|
2065 |
+}); |
|
2066 |
+ |
|
2067 |
+Faye.Transport.register('long-polling', Faye.Transport.XHR); |
|
2068 |
+ |
|
2069 |
+Faye.Transport.CORS = Faye.extend(Faye.Class(Faye.Transport, { |
|
2070 |
+ request: function(message, timeout) { |
|
2071 |
+ var xhrClass = Faye.ENV.XDomainRequest ? XDomainRequest : XMLHttpRequest, |
|
2072 |
+ xhr = new xhrClass(), |
|
2073 |
+ retry = this.retry(message, timeout), |
|
2074 |
+ self = this; |
|
2075 |
+ |
|
2076 |
+ xhr.open('POST', this.endpoint, true); |
|
2077 |
+ if (xhr.setRequestHeader) xhr.setRequestHeader('Pragma', 'no-cache'); |
|
2078 |
+ |
|
2079 |
+ var cleanUp = function() { |
|
2080 |
+ if (!xhr) return false; |
|
2081 |
+ xhr.onload = xhr.onerror = xhr.ontimeout = xhr.onprogress = null; |
|
2082 |
+ xhr = null; |
|
2083 |
+ Faye.ENV.clearTimeout(timer); |
|
2084 |
+ return true; |
|
2085 |
+ }; |
|
2086 |
+ |
|
2087 |
+ xhr.onload = function() { |
|
2088 |
+ var parsedMessage = null; |
|
2089 |
+ try { |
|
2090 |
+ parsedMessage = JSON.parse(xhr.responseText); |
|
2091 |
+ } catch (e) {} |
|
2092 |
+ |
|
2093 |
+ cleanUp(); |
|
2094 |
+ |
|
2095 |
+ if (parsedMessage) { |
|
2096 |
+ self.receive(parsedMessage); |
|
2097 |
+ self.trigger('up'); |
|
2098 |
+ } else { |
|
2099 |
+ retry(); |
|
2100 |
+ self.trigger('down'); |
|
2101 |
+ } |
|
2102 |
+ }; |
|
2103 |
+ |
|
2104 |
+ var onerror = function() { |
|
2105 |
+ cleanUp(); |
|
2106 |
+ retry(); |
|
2107 |
+ self.trigger('down'); |
|
2108 |
+ }; |
|
2109 |
+ var timer = Faye.ENV.setTimeout(onerror, 1.5 * 1000 * timeout); |
|
2110 |
+ xhr.onerror = onerror; |
|
2111 |
+ xhr.ontimeout = onerror; |
|
2112 |
+ |
|
2113 |
+ xhr.onprogress = function() {}; |
|
2114 |
+ xhr.send('message=' + encodeURIComponent(Faye.toJSON(message))); |
|
2115 |
+ } |
|
2116 |
+}), { |
|
2117 |
+ isUsable: function(client, endpoint, callback, context) { |
|
2118 |
+ if (Faye.URI.parse(endpoint).isSameOrigin()) |
|
2119 |
+ return callback.call(context, false); |
|
2120 |
+ |
|
2121 |
+ if (Faye.ENV.XDomainRequest) |
|
2122 |
+ return callback.call(context, Faye.URI.parse(endpoint).protocol === |
|
2123 |
+ Faye.URI.parse(Faye.ENV.location).protocol); |
|
2124 |
+ |
|
2125 |
+ if (Faye.ENV.XMLHttpRequest) { |
|
2126 |
+ var xhr = new Faye.ENV.XMLHttpRequest(); |
|
2127 |
+ return callback.call(context, xhr.withCredentials !== undefined); |
|
2128 |
+ } |
|
2129 |
+ return callback.call(context, false); |
|
2130 |
+ } |
|
2131 |
+}); |
|
2132 |
+ |
|
2133 |
+Faye.Transport.register('cross-origin-long-polling', Faye.Transport.CORS); |
|
2134 |
+ |
|
2135 |
+ |
|
2136 |
+Faye.Transport.JSONP = Faye.extend(Faye.Class(Faye.Transport, { |
|
2137 |
+ shouldFlush: function(messages) { |
|
2138 |
+ var params = { |
|
2139 |
+ message: Faye.toJSON(messages), |
|
2140 |
+ jsonp: '__jsonp' + Faye.Transport.JSONP._cbCount + '__' |
|
2141 |
+ }; |
|
2142 |
+ var location = Faye.URI.parse(this.endpoint, params).toURL(); |
|
2143 |
+ return location.length >= Faye.Transport.MAX_URL_LENGTH; |
|
2144 |
+ }, |
|
2145 |
+ |
|
2146 |
+ request: function(messages, timeout) { |
|
2147 |
+ var params = {message: Faye.toJSON(messages)}, |
|
2148 |
+ head = document.getElementsByTagName('head')[0], |
|
2149 |
+ script = document.createElement('script'), |
|
2150 |
+ callbackName = Faye.Transport.JSONP.getCallbackName(), |
|
2151 |
+ location = Faye.URI.parse(this.endpoint, params), |
|
2152 |
+ retry = this.retry(messages, timeout), |
|
2153 |
+ self = this; |
|
2154 |
+ |
|
2155 |
+ Faye.ENV[callbackName] = function(data) { |
|
2156 |
+ cleanUp(); |
|
2157 |
+ self.receive(data); |
|
2158 |
+ self.trigger('up'); |
|
2159 |
+ }; |
|
2160 |
+ |
|
2161 |
+ var timer = Faye.ENV.setTimeout(function() { |
|
2162 |
+ cleanUp(); |
|
2163 |
+ retry(); |
|
2164 |
+ self.trigger('down'); |
|
2165 |
+ }, 1.5 * 1000 * timeout); |
|
2166 |
+ |
|
2167 |
+ var cleanUp = function() { |
|
2168 |
+ if (!Faye.ENV[callbackName]) return false; |
|
2169 |
+ Faye.ENV[callbackName] = undefined; |
|
2170 |
+ try { delete Faye.ENV[callbackName] } catch (e) {} |
|
2171 |
+ Faye.ENV.clearTimeout(timer); |
|
2172 |
+ script.parentNode.removeChild(script); |
|
2173 |
+ return true; |
|
2174 |
+ }; |
|
2175 |
+ |
|
2176 |
+ location.params.jsonp = callbackName; |
|
2177 |
+ script.type = 'text/javascript'; |
|
2178 |
+ script.src = location.toURL(); |
|
2179 |
+ head.appendChild(script); |
|
2180 |
+ } |
|
2181 |
+}), { |
|
2182 |
+ _cbCount: 0, |
|
2183 |
+ |
|
2184 |
+ getCallbackName: function() { |
|
2185 |
+ this._cbCount += 1; |
|
2186 |
+ return '__jsonp' + this._cbCount + '__'; |
|
2187 |
+ }, |
|
2188 |
+ |
|
2189 |
+ isUsable: function(client, endpoint, callback, context) { |
|
2190 |
+ callback.call(context, true); |
|
2191 |
+ } |
|
2192 |
+}); |
|
2193 |
+ |
|
2194 |
+Faye.Transport.register('callback-polling', Faye.Transport.JSONP); |
@@ -3,16 +3,16 @@ angular.module('avalancheDocsApp') |
||
3 | 3 |
var pages = []; |
4 | 4 |
var current_page = {}; |
5 | 5 |
|
6 |
- this.find = function(page){ |
|
6 |
+ this.find = function(group, page){ |
|
7 | 7 |
if( pages.length > 0) { |
8 |
- searchForPage(page); |
|
8 |
+ searchForPage(group, page); |
|
9 | 9 |
} else { |
10 | 10 |
$http({ |
11 | 11 |
method: 'GET', |
12 |
- url: 'data/rest-api-v1-pages.json' |
|
12 |
+ url: 'data/page-list.json' |
|
13 | 13 |
}).then(function(data) { |
14 | 14 |
pages = data.data; |
15 |
- searchForPage(page); |
|
15 |
+ searchForPage(group, page); |
|
16 | 16 |
$rootScope.$broadcast('get-pages:finished'); |
17 | 17 |
}); |
18 | 18 |
} |
@@ -22,16 +22,16 @@ angular.module('avalancheDocsApp') |
||
22 | 22 |
return current_page; |
23 | 23 |
} |
24 | 24 |
|
25 |
- this.all = function(){ |
|
26 |
- return pages; |
|
25 |
+ this.all = function(group){ |
|
26 |
+ return pages[group]; |
|
27 | 27 |
} |
28 | 28 |
|
29 |
- searchForPage = function(page) { |
|
29 |
+ searchForPage = function(group, page) { |
|
30 | 30 |
//console.log("searching for " + page); |
31 |
- for (var i = 0; i < pages.length; i++) { |
|
32 |
- //console.log(pages[i].slug); |
|
33 |
- if (pages[i].slug == page) { |
|
34 |
- current_page = pages[i]; |
|
31 |
+ for (var i = 0; i < pages[group].length; i++) { |
|
32 |
+ //console.log(pages[group][i].slug); |
|
33 |
+ if (pages[group][i].slug == page) { |
|
34 |
+ current_page = pages[group][i]; |
|
35 | 35 |
return current_page; |
36 | 36 |
} |
37 | 37 |
} |
@@ -0,0 +1,37 @@ |
||
1 |
+angular.module('avalancheDocsApp') |
|
2 |
+.factory('RealtimeService', [ '$rootScope', '$http', function($rootScope, $http) { |
|
3 |
+ var FayeServerURL = 'https://avalanche-realtime.herokuapp.com/faye' |
|
4 |
+ |
|
5 |
+ var client = new Faye.Client(FayeServerURL); |
|
6 |
+ |
|
7 |
+ client.on('transport:down', function() { |
|
8 |
+ console.log("offline"); |
|
9 |
+ $rootScope.$broadcast('realtime:offline'); |
|
10 |
+ }); |
|
11 |
+ |
|
12 |
+ client.on('transport:up', function() { |
|
13 |
+ console.log("online"); |
|
14 |
+ $rootScope.$broadcast('realtime:online'); |
|
15 |
+ }); |
|
16 |
+ |
|
17 |
+ return { |
|
18 |
+ publish: function(channel, message) { |
|
19 |
+ client.publish(channel, message); |
|
20 |
+ }, |
|
21 |
+ |
|
22 |
+ subscribe: function(channel, callback) { |
|
23 |
+ client.subscribe(channel, callback).then(function() { |
|
24 |
+ console.log("subscribing to " + channel) |
|
25 |
+ }); |
|
26 |
+ }, |
|
27 |
+ disconect: function() { |
|
28 |
+ console.log("Disconecting...") |
|
29 |
+ client.disconnect(); |
|
30 |
+ $rootScope.$broadcast('realtime:offline'); |
|
31 |
+ }, |
|
32 |
+ connect: function() { |
|
33 |
+ client = new Faye.Client(FayeServerURL); |
|
34 |
+ $rootScope.$broadcast('realtime:online'); |
|
35 |
+ } |
|
36 |
+ } |
|
37 |
+}]); |
@@ -135,3 +135,58 @@ |
||
135 | 135 |
::-webkit-scrollbar-thumb { |
136 | 136 |
background-color: #AFB4BF; |
137 | 137 |
} |
138 |
+ |
|
139 |
+.angular-json-explorer { |
|
140 |
+ font-family: monospace; |
|
141 |
+ font-size: 1em; |
|
142 |
+ white-space: pre-wrap; |
|
143 |
+} |
|
144 |
+ |
|
145 |
+.circle-ok { |
|
146 |
+ width: 10px; |
|
147 |
+ background-color: #5FB760; |
|
148 |
+ border-radius: 50px; |
|
149 |
+ height: 10px; |
|
150 |
+ float: left; |
|
151 |
+ margin-top: 8px; |
|
152 |
+ margin-right: 5px; |
|
153 |
+} |
|
154 |
+ |
|
155 |
+.circle-error { |
|
156 |
+ width: 10px; |
|
157 |
+ background-color: #D75452; |
|
158 |
+ border-radius: 50px; |
|
159 |
+ height: 10px; |
|
160 |
+ float: left; |
|
161 |
+ margin-top: 8px; |
|
162 |
+ margin-right: 5px; |
|
163 |
+} |
|
164 |
+ |
|
165 |
+.bd-callout .bd-callout { |
|
166 |
+ margin-top: -.25rem; |
|
167 |
+} |
|
168 |
+ |
|
169 |
+.bd-callout { |
|
170 |
+ padding: 1.25rem; |
|
171 |
+ margin-top: 1.25rem; |
|
172 |
+ margin-bottom: 1.25rem; |
|
173 |
+ border: 1px solid #eee; |
|
174 |
+ border-left-width: .25rem; |
|
175 |
+ border-radius: .25rem; |
|
176 |
+} |
|
177 |
+ |
|
178 |
+.bd-callout.bd-callout-danger { |
|
179 |
+ border-left-color: #d9534f; |
|
180 |
+} |
|
181 |
+ |
|
182 |
+.bd-callout-danger h4 { |
|
183 |
+ color: #d9534f; |
|
184 |
+} |
|
185 |
+.bd-callout h4 { |
|
186 |
+ margin-top: 0; |
|
187 |
+ margin-bottom: .25rem; |
|
188 |
+} |
|
189 |
+ |
|
190 |
+.bd-callout p:last-child { |
|
191 |
+ margin-bottom: 0; |
|
192 |
+} |
@@ -1,17 +1,36 @@ |
||
1 | 1 |
<div class="container-fluid" > |
2 | 2 |
<div class="row"> |
3 |
- <div class="col-sm-3 col-md-2 sidebar sidebar-main"> |
|
4 |
- <h1>docs</h1> |
|
5 |
- <ul class="nav nav-sidebar"> |
|
6 |
- <li class="active"><a ui-sref="getting-started">Getting Started<span class="sr-only">(current)</span></a></li> |
|
7 |
- <li><a ui-sref="rulebook.intro">Rulebook</a></li> |
|
8 |
- <li><a ui-sref="styleguide">Styleguide</a></li> |
|
9 |
- <li><a ui-sref="rest-api-v1({ id: 'get-missions' })">Restfull API v1 </a></li> |
|
10 |
- <li><a ui-sref="realtime-api-v1">Realtime API v1</a></li> |
|
11 |
- </ul> |
|
12 |
- </div> |
|
3 |
+ <ng-include src="'views/helpers/sidebar.html'"></ng-include> |
|
13 | 4 |
<div class="col-sm-9 col-sm-offset-3 col-md-10 col-md-offset-2 main" ui-view> |
14 |
- |
|
5 |
+ <div class="jumbotron jumbotron-fluid" style="margin-left: -40px; margin-right: -40px; margin-top: -20px;"> |
|
6 |
+ <div class="container"> |
|
7 |
+ <h1 class="display-3">Avalanche Docs</h1> |
|
8 |
+ <p class="lead">Welcome to the Avalanche Network documentation getting started guide.</p> |
|
9 |
+ </div> |
|
10 |
+ </div> |
|
11 |
+ <div class="row"> |
|
12 |
+ <div class="col-sm-4"> |
|
13 |
+ <div class="card card-block"> |
|
14 |
+ <h3 class="card-title">Rulebook</h3> |
|
15 |
+ <p class="card-text">Detailed information about all aspects of the Avalanhce Network mechanics.</p> |
|
16 |
+ <a ui-sref="rulebook({ id: 'intro'})" class="btn btn-primary">View rulebook</a> |
|
17 |
+ </div> |
|
18 |
+ </div> |
|
19 |
+ <div class="col-sm-4"> |
|
20 |
+ <div class="card card-block"> |
|
21 |
+ <h3 class="card-title">Styleguide</h3> |
|
22 |
+ <p class="card-text">Avalanche logo, color pallet, UI Theme/template and the web font icon set.</p> |
|
23 |
+ <a href="#" class="btn btn-primary">View styleguide</a> |
|
24 |
+ </div> |
|
25 |
+ </div> |
|
26 |
+ <div class="col-sm-4"> |
|
27 |
+ <div class="card card-block"> |
|
28 |
+ <h3 class="card-title">Restfull API v1</h3> |
|
29 |
+ <p class="card-text">Detailed information about each API endpoint with examples and tests.</p> |
|
30 |
+ <a ui-sref="rest-api-v1({ id: 'get-missions' })" class="btn btn-primary">View API Docs</a> |
|
31 |
+ </div> |
|
32 |
+ </div> |
|
33 |
+ </div> |
|
15 | 34 |
</div> |
16 | 35 |
</div> |
17 | 36 |
</div> |
@@ -4,7 +4,7 @@ |
||
4 | 4 |
<td colspan="2">{{status(response.status).code}}</td> |
5 | 5 |
</tr> |
6 | 6 |
<tr> |
7 |
- <td colspan="2"><json-formatter json="response.data" open="1" style="overflow-x: hidden;"></json-formatter></td> |
|
7 |
+ <td colspan="2"><json-explorer data="response.data"></json-explorer></td> |
|
8 | 8 |
</tr> |
9 | 9 |
<tr> |
10 | 10 |
<td>Method</td> |
@@ -0,0 +1,14 @@ |
||
1 |
+<div class="col-sm-3 col-md-2 sidebar sidebar-main"> |
|
2 |
+ <h1>docs</h1> |
|
3 |
+ <ul class="nav nav-sidebar"> |
|
4 |
+ <li ng-class="{ active: navActivePage('getting-started')}"><a ui-sref="getting-started">Getting Started</a></li> |
|
5 |
+ <li ng-hide="'false'"><a ui-sref="tutorials">Tutorials</a></li> |
|
6 |
+ <li ng-hide="'false'"><a ui-sref="manifest">Manifest</a></li> |
|
7 |
+ <li ng-class="{ active: navActivePage('rulebook')}"><a ui-sref="rulebook({ id: 'intro'})">Rulebook</a></li> |
|
8 |
+ <li ng-hide="'false'"><a ui-sref="faq">FAQ</a></li> |
|
9 |
+ <li ng-hide="'false'"><a ui-sref="styleguide">Styleguide</a></li> |
|
10 |
+ <li ng-hide="'true'"><a ui-sref="theme">Theme</a></li> |
|
11 |
+ <li ng-class="{ active: navActivePage('rest-api-v1')}"><a ui-sref="rest-api-v1({ id: 'get-missions' })">Restfull API v1 <span class="sr-only">(current)</span></a></li> |
|
12 |
+ <li ng-class="{ active: navActivePage('realtime-api-v1')}"><a ui-sref="realtime-api-v1({ id: 'realtime-mission-activity' })">Realtime API v1</a></li> |
|
13 |
+ </ul> |
|
14 |
+</div> |
@@ -0,0 +1,5 @@ |
||
1 |
+<div class="bd-callout bd-callout-danger"> |
|
2 |
+<h4 id="cross-browser-compatibility">Not Implemented</h4> |
|
3 |
+ |
|
4 |
+<p>This feature has not been implemented yet. It has been planed to be implemented soon. Hold tight.</p> |
|
5 |
+</div> |
@@ -0,0 +1,78 @@ |
||
1 |
+<div class="container-fluid" > |
|
2 |
+ <div class="row"> |
|
3 |
+ <ng-include src="'views/helpers/sidebar.html'"></ng-include> |
|
4 |
+ <div class="col-sm-4 col-md-3 col-md-offset-2 col-sm-offset-3 sidebar sidebar-secondary"> |
|
5 |
+ <ul class="nav nav-sidebar"> |
|
6 |
+ <li ng-repeat="page in pageList" ng-class="{ active: isActive(page.pageUrl)}"> |
|
7 |
+ <a ui-sref="realtime-api-v1({ id: page.slug})"> |
|
8 |
+ <h5>{{page.title}}</h5> |
|
9 |
+ <p class="endpoint-description"><i>{{page.tagline}}</i></p> |
|
10 |
+ </a> |
|
11 |
+ </li> |
|
12 |
+ </ul> |
|
13 |
+ </div> |
|
14 |
+ <div class="col-sm-5 col-sm-offset-7 col-md-7 col-md-offset-5 main"> |
|
15 |
+ <div class="api-endpoint"> |
|
16 |
+ <h2 style="margin-bottom: 0px;">{{pageData.title}}</h2> |
|
17 |
+ <small class="api-endpoint"><span ng-class="callTypeBadge(pageData.endpoint.type)">{{pageData.endpoint.type}}</span> {{pageData.endpoint.base}}</small> |
|
18 |
+ <hr> |
|
19 |
+ |
|
20 |
+ <div ng-if="pageData.description != undefined"> |
|
21 |
+ <p ng-bind-html="pageData.description"></p> |
|
22 |
+ </div> |
|
23 |
+ |
|
24 |
+ <ng-include src="'views/helpers/under-construction.html'" ng-hide="pageData.implemented"></ng-include> |
|
25 |
+ |
|
26 |
+ <div class="top-spacer" ng-show="pageData.implemented"> |
|
27 |
+ <h5>Subscribe to channel</h5> |
|
28 |
+ <p class="top-spacer"> |
|
29 |
+ To subscribe, first get the mission chanel name using the restfull API and authentication scheme. After you have the mission chanel name, use faye to subscribe to that chanel. |
|
30 |
+ </p> |
|
31 |
+ <div class="row top-spacer"> |
|
32 |
+ <div class="col-sm-9"> |
|
33 |
+ <fieldset class="form-group"> |
|
34 |
+ <label for="mission-slug">Mission Channel Name</label> |
|
35 |
+ <input type="text" class="form-control" id="mission-slug" placeholder="/missions/example-mission" ng-model="MissionChanelName" ng-hide="has_connection"> |
|
36 |
+ <input type="text" class="form-control" id="mission-slug" placeholder="/missions/example-mission" ng-model="MissionChanelName" ng-show="has_connection" disabled> |
|
37 |
+ </fieldset> |
|
38 |
+ </div> |
|
39 |
+ <div class="col-sm-3"> |
|
40 |
+ <fieldset class="form-group" style="margin-top: 35px;"> |
|
41 |
+ <button type="submit" class="btn btn-success btn-sm" ng-click="subscribe(MissionChanelName)" ng-hide="has_connection">Connect</button> |
|
42 |
+ <button type="submit" class="btn btn-danger-outline btn-sm" ng-click="disconect()" ng-show="has_connection">Disconect</button> |
|
43 |
+ </fieldset> |
|
44 |
+ </div> |
|
45 |
+ </div> |
|
46 |
+ <div class="row" ng-show="subscribing"> |
|
47 |
+ <div class="table-responsive col-sm-12" > |
|
48 |
+ <table class="table table-bordered top-spacer" > |
|
49 |
+ <tr> |
|
50 |
+ <td colspan="2"><span ng-bind-html="connection_status"></span><i class="pull-right" ng-bind="messages.length + ' messages received'"></i></td> |
|
51 |
+ </tr> |
|
52 |
+ <tr> |
|
53 |
+ <td colspan="2"><json-explorer data="messages"></json-explorer></td> |
|
54 |
+ </tr> |
|
55 |
+ </table> |
|
56 |
+ </div> |
|
57 |
+ </div> |
|
58 |
+ <div class="row" ng-show="subscribing && logged_in"> |
|
59 |
+ <div class="col-sm-9"> |
|
60 |
+ <fieldset class="form-group"> |
|
61 |
+ <label for="mission-slug">Chat message</label> |
|
62 |
+ <input type="text" class="form-control" id="mission-slug" placeholder="" ng-model="chatMessage"> |
|
63 |
+ </fieldset> |
|
64 |
+ </div> |
|
65 |
+ <div class="col-sm-3"> |
|
66 |
+ <fieldset class="form-group" style="margin-top: 35px;"> |
|
67 |
+ <button type="submit" class="btn btn-success btn-sm" ng-click="getData(pageData.endpoint.base)">Send Msg</button> |
|
68 |
+ </fieldset> |
|
69 |
+ </div> |
|
70 |
+ </div> |
|
71 |
+ </div> |
|
72 |
+ |
|
73 |
+ |
|
74 |
+ |
|
75 |
+ |
|
76 |
+ </div> |
|
77 |
+ </div> |
|
78 |
+</div> |
@@ -1,15 +1,6 @@ |
||
1 | 1 |
<div class="container-fluid" > |
2 | 2 |
<div class="row"> |
3 |
- <div class="col-sm-3 col-md-2 sidebar sidebar-main"> |
|
4 |
- <h1>docs</h1> |
|
5 |
- <ul class="nav nav-sidebar"> |
|
6 |
- <li><a ui-sref="getting-started">Getting Started</a></li> |
|
7 |
- <li><a ui-sref="rulebook.intro">Rulebook</a></li> |
|
8 |
- <li><a ui-sref="styleguide">Styleguide</a></li> |
|
9 |
- <li class="active"><a ui-sref="rest-api-v1({ id: 'get-missions' })">Restfull API v1 <span class="sr-only">(current)</span></a></li> |
|
10 |
- <li><a ui-sref="realtime-api-v1">Realtime API v1</a></li> |
|
11 |
- </ul> |
|
12 |
- </div> |
|
3 |
+ <ng-include src="'views/helpers/sidebar.html'"></ng-include> |
|
13 | 4 |
<div class="col-sm-4 col-md-3 col-md-offset-2 col-sm-offset-3 sidebar sidebar-secondary"> |
14 | 5 |
<ul class="nav nav-sidebar"> |
15 | 6 |
<li ng-repeat="page in pageList" ng-class="{ active: isActive(page.pageUrl)}"> |
@@ -31,6 +22,8 @@ |
||
31 | 22 |
<p ng-bind-html="pageData.description"></p> |
32 | 23 |
</div> |
33 | 24 |
|
25 |
+ <ng-include src="'views/helpers/under-construction.html'" ng-hide="pageData.implemented"></ng-include> |
|
26 |
+ |
|
34 | 27 |
<div ng-if="pageData.variables.length > 0"> |
35 | 28 |
<h5 class="top-spacer">Variables</h5> |
36 | 29 |
<table class="table table-bordered top-spacer"> |
@@ -50,20 +43,22 @@ |
||
50 | 43 |
</tabset> |
51 | 44 |
</div> |
52 | 45 |
|
53 |
- <h5>Responses</h5> |
|
54 |
- <form class="top-spacer"> |
|
55 |
- <ng-include src="'views/helpers/api-token-field.html'"></ng-include> |
|
56 |
- <div class="row"> |
|
57 |
- <div class="col-sm-3" ng-repeat="variable in pageData.variables"> |
|
58 |
- <fieldset class="form-group"> |
|
59 |
- <label for="{{variable.name}}">{{variable.name}}</label> |
|
60 |
- <input type="text" class="form-control" id="{{variable.name}}" placeholder="{{variable.placeholder}}" ng-model="variable.value"> |
|
61 |
- </fieldset> |
|
62 |
- </div> |
|
63 |
- </div> |
|
64 |
- <ng-include src="'views/helpers/api-endpoint-field.html'"></ng-include> |
|
65 |
- </form> |
|
66 |
- <ng-include src="'views/helpers/api-response.html'"></ng-include> |
|
46 |
+ <div ng-show="pageData.implemented"> |
|
47 |
+ <h5>Responses</h5> |
|
48 |
+ <form class="top-spacer"> |
|
49 |
+ <ng-include src="'views/helpers/api-token-field.html'"></ng-include> |
|
50 |
+ <div class="row"> |
|
51 |
+ <div class="col-sm-3" ng-repeat="variable in pageData.variables"> |
|
52 |
+ <fieldset class="form-group"> |
|
53 |
+ <label for="{{variable.name}}">{{variable.name}}</label> |
|
54 |
+ <input type="text" class="form-control" id="{{variable.name}}" placeholder="{{variable.placeholder}}" ng-model="variable.value"> |
|
55 |
+ </fieldset> |
|
56 |
+ </div> |
|
57 |
+ </div> |
|
58 |
+ <ng-include src="'views/helpers/api-endpoint-field.html'"></ng-include> |
|
59 |
+ </form> |
|
60 |
+ <ng-include src="'views/helpers/api-response.html'"></ng-include> |
|
61 |
+ </div> |
|
67 | 62 |
|
68 | 63 |
</div> |
69 | 64 |
</div> |
@@ -0,0 +1,22 @@ |
||
1 |
+<div class="container-fluid" > |
|
2 |
+ <div class="row"> |
|
3 |
+ <ng-include src="'views/helpers/sidebar.html'"></ng-include> |
|
4 |
+ <div class="col-sm-4 col-md-3 col-md-offset-2 col-sm-offset-3 sidebar sidebar-secondary"> |
|
5 |
+ <ul class="nav nav-sidebar"> |
|
6 |
+ <li ng-repeat="page in pageList" ng-class="{ active: isActive(page.pageUrl)}"> |
|
7 |
+ <a ui-sref="rulebook({ id: page.slug})"> |
|
8 |
+ <h5>{{page.title}}</h5> |
|
9 |
+ <p class="endpoint-description"><i>{{page.tagline}}</i></p> |
|
10 |
+ </a> |
|
11 |
+ </li> |
|
12 |
+ </ul> |
|
13 |
+ </div> |
|
14 |
+ <div class="col-sm-5 col-sm-offset-7 col-md-7 col-md-offset-5 main" ui-view> |
|
15 |
+ <div class="api-endpoint"> |
|
16 |
+ <h2 style="margin-bottom: 0px;">{{pageData.title}}</h2> |
|
17 |
+ <hr> |
|
18 |
+ </div> |
|
19 |
+ <div btf-markdown ng-include="pageData.content"> |
|
20 |
+ </div> |
|
21 |
+ </div> |
|
22 |
+</div> |
@@ -1,39 +0,0 @@ |
||
1 |
-<div class="container-fluid" > |
|
2 |
- <div class="row"> |
|
3 |
- <div class="col-sm-3 col-md-2 sidebar sidebar-main"> |
|
4 |
- <h1>docs</h1> |
|
5 |
- <ul class="nav nav-sidebar"> |
|
6 |
- <li><a ui-sref="getting-started">Getting Started</a></li> |
|
7 |
- <li class="active"><a ui-sref="rulebook.intro">Rulebook <span class="sr-only">(current)</span></a></li> |
|
8 |
- <li><a ui-sref="styleguide">Styleguide</a></li> |
|
9 |
- <li><a ui-sref="rest-api-v1.get-missions">Restfull API v1</a></li> |
|
10 |
- <li><a ui-sref="realtime-api-v1">Realtime API v1</a></li> |
|
11 |
- </ul> |
|
12 |
- </div> |
|
13 |
- <div class="col-sm-4 col-md-3 col-md-offset-2 col-sm-offset-3 sidebar sidebar-secondary"> |
|
14 |
- <ul class="nav nav-sidebar"> |
|
15 |
- <li ng-class="{ active: isActive('/rulebook/intro')}"> |
|
16 |
- <a ui-sref="rulebook.intro"> |
|
17 |
- <h5>Introduction</h5> |
|
18 |
- <p class="endpoint-description"><i>What is Avalanche Network?</i></p> |
|
19 |
- </a> |
|
20 |
- </li> |
|
21 |
- <li ng-class="{ active: isActive('/rulebook/mission')}"> |
|
22 |
- <a ui-sref="rulebook.mission"> |
|
23 |
- <h5>Mission Basics</h5> |
|
24 |
- <p class="endpoint-description"><i>Mechanics of how a mission works</i></p> |
|
25 |
- </a> |
|
26 |
- </li> |
|
27 |
- <li ng-class="{ active: isActive('/rulebook/tasks-validations')}"> |
|
28 |
- <a ui-sref="rulebook.tasks-validations"> |
|
29 |
- <h5>Tasks & Validations</h5> |
|
30 |
- <p class="endpoint-description"><i>How to automaticaly validate tasks</i></p> |
|
31 |
- </a> |
|
32 |
- </li> |
|
33 |
- </ul> |
|
34 |
- </div> |
|
35 |
- <div class="col-sm-5 col-sm-offset-7 col-md-7 col-md-offset-5 main" ui-view> |
|
36 |
- |
|
37 |
- </div> |
|
38 |
- </div> |
|
39 |
-</div> |
@@ -1,6 +0,0 @@ |
||
1 |
- |
|
2 |
- |
|
3 |
-<div class="api-endpoint"> |
|
4 |
- <h2 style="margin-bottom: 0px;">Introduction</h2> |
|
5 |
- <hr> |
|
6 |
-</div> |
@@ -1,6 +0,0 @@ |
||
1 |
- |
|
2 |
- |
|
3 |
-<div class="api-endpoint"> |
|
4 |
- <h2 style="margin-bottom: 0px;">Mission</h2> |
|
5 |
- <hr> |
|
6 |
-</div> |
@@ -14,7 +14,9 @@ |
||
14 | 14 |
"angular-highlightjs": "~0.4.3", |
15 | 15 |
"json-formatter": "~0.3.1", |
16 | 16 |
"angular-spinner": "~0.6.2", |
17 |
- "angular-ui-router": "~0.2.15" |
|
17 |
+ "angular-ui-router": "~0.2.15", |
|
18 |
+ "angular-markdown-directive": "~0.3.1", |
|
19 |
+ "ng-json-explorer": "*" |
|
18 | 20 |
}, |
19 | 21 |
"devDependencies": { |
20 | 22 |
"angular-mocks": "^1.3.0" |
@@ -37,6 +37,10 @@ module.exports = function(config) { |
||
37 | 37 |
'bower_components/spin.js/spin.js', |
38 | 38 |
'bower_components/angular-spinner/angular-spinner.js', |
39 | 39 |
'bower_components/angular-ui-router/release/angular-ui-router.js', |
40 |
+ 'bower_components/showdown/src/showdown.js', |
|
41 |
+ 'bower_components/angularjs/angular.js', |
|
42 |
+ 'bower_components/angular-markdown-directive/markdown.js', |
|
43 |
+ 'bower_components/ng-json-explorer/dist/angular-json-explorer.js', |
|
40 | 44 |
'bower_components/angular-mocks/angular-mocks.js', |
41 | 45 |
// endbower |
42 | 46 |
"app/scripts/**/*.js", |